@haneullabs/signers 0.1.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 +555 -0
- package/README.md +200 -0
- package/aws/package.json +6 -0
- package/dist/cjs/aws/aws-client.d.ts +43 -0
- package/dist/cjs/aws/aws-client.js +79 -0
- package/dist/cjs/aws/aws-client.js.map +7 -0
- package/dist/cjs/aws/aws-kms-signer.d.ts +61 -0
- package/dist/cjs/aws/aws-kms-signer.js +114 -0
- package/dist/cjs/aws/aws-kms-signer.js.map +7 -0
- package/dist/cjs/aws/aws4fetch.d.ts +125 -0
- package/dist/cjs/aws/aws4fetch.js +382 -0
- package/dist/cjs/aws/aws4fetch.js.map +7 -0
- package/dist/cjs/aws/index.d.ts +5 -0
- package/dist/cjs/aws/index.js +25 -0
- package/dist/cjs/aws/index.js.map +7 -0
- package/dist/cjs/gcp/gcp-kms-client.d.ts +68 -0
- package/dist/cjs/gcp/gcp-kms-client.js +147 -0
- package/dist/cjs/gcp/gcp-kms-client.js.map +7 -0
- package/dist/cjs/gcp/index.d.ts +4 -0
- package/dist/cjs/gcp/index.js +25 -0
- package/dist/cjs/gcp/index.js.map +7 -0
- package/dist/cjs/ledger/bcs.d.ts +14 -0
- package/dist/cjs/ledger/bcs.js +85 -0
- package/dist/cjs/ledger/bcs.js.map +7 -0
- package/dist/cjs/ledger/index.d.ts +66 -0
- package/dist/cjs/ledger/index.js +158 -0
- package/dist/cjs/ledger/index.js.map +7 -0
- package/dist/cjs/ledger/objects.d.ts +5 -0
- package/dist/cjs/ledger/objects.js +60 -0
- package/dist/cjs/ledger/objects.js.map +7 -0
- package/dist/cjs/package.json +5 -0
- package/dist/cjs/utils/utils.d.ts +18 -0
- package/dist/cjs/utils/utils.js +85 -0
- package/dist/cjs/utils/utils.js.map +7 -0
- package/dist/cjs/webcrypto/index.d.ts +26 -0
- package/dist/cjs/webcrypto/index.js +112 -0
- package/dist/cjs/webcrypto/index.js.map +7 -0
- package/dist/esm/aws/aws-client.d.ts +43 -0
- package/dist/esm/aws/aws-client.js +59 -0
- package/dist/esm/aws/aws-client.js.map +7 -0
- package/dist/esm/aws/aws-kms-signer.d.ts +61 -0
- package/dist/esm/aws/aws-kms-signer.js +94 -0
- package/dist/esm/aws/aws-kms-signer.js.map +7 -0
- package/dist/esm/aws/aws4fetch.d.ts +125 -0
- package/dist/esm/aws/aws4fetch.js +362 -0
- package/dist/esm/aws/aws4fetch.js.map +7 -0
- package/dist/esm/aws/index.d.ts +5 -0
- package/dist/esm/aws/index.js +5 -0
- package/dist/esm/aws/index.js.map +7 -0
- package/dist/esm/gcp/gcp-kms-client.d.ts +68 -0
- package/dist/esm/gcp/gcp-kms-client.js +127 -0
- package/dist/esm/gcp/gcp-kms-client.js.map +7 -0
- package/dist/esm/gcp/index.d.ts +4 -0
- package/dist/esm/gcp/index.js +5 -0
- package/dist/esm/gcp/index.js.map +7 -0
- package/dist/esm/ledger/bcs.d.ts +14 -0
- package/dist/esm/ledger/bcs.js +70 -0
- package/dist/esm/ledger/bcs.js.map +7 -0
- package/dist/esm/ledger/index.d.ts +66 -0
- package/dist/esm/ledger/index.js +138 -0
- package/dist/esm/ledger/index.js.map +7 -0
- package/dist/esm/ledger/objects.d.ts +5 -0
- package/dist/esm/ledger/objects.js +40 -0
- package/dist/esm/ledger/objects.js.map +7 -0
- package/dist/esm/package.json +5 -0
- package/dist/esm/utils/utils.d.ts +18 -0
- package/dist/esm/utils/utils.js +65 -0
- package/dist/esm/utils/utils.js.map +7 -0
- package/dist/esm/webcrypto/index.d.ts +26 -0
- package/dist/esm/webcrypto/index.js +92 -0
- package/dist/esm/webcrypto/index.js.map +7 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/gcp/package.json +6 -0
- package/ledger/package.json +6 -0
- package/package.json +76 -0
- package/src/aws/aws-client.ts +107 -0
- package/src/aws/aws-kms-signer.ts +111 -0
- package/src/aws/aws4fetch.ts +502 -0
- package/src/aws/index.ts +9 -0
- package/src/gcp/gcp-kms-client.ts +165 -0
- package/src/gcp/index.ts +9 -0
- package/src/ledger/bcs.ts +87 -0
- package/src/ledger/index.ts +164 -0
- package/src/ledger/objects.ts +56 -0
- package/src/utils/utils.ts +119 -0
- package/src/webcrypto/index.ts +108 -0
- package/webcrypto/package.json +6 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Copyright (c) Mysten Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { KeyManagementServiceClient } from '@google-cloud/kms';
|
|
4
|
+
import type { PublicKey, SignatureFlag } from '@haneullabs/haneul/cryptography';
|
|
5
|
+
import { SIGNATURE_FLAG_TO_SCHEME, Signer } from '@haneullabs/haneul/cryptography';
|
|
6
|
+
import { Secp256k1PublicKey } from '@haneullabs/haneul/keypairs/secp256k1';
|
|
7
|
+
import { Secp256r1PublicKey } from '@haneullabs/haneul/keypairs/secp256r1';
|
|
8
|
+
import { fromBase64 } from '@haneullabs/haneul/utils';
|
|
9
|
+
|
|
10
|
+
import { getConcatenatedSignature, publicKeyFromDER } from '../utils/utils.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Configuration options for initializing the GcpKmsSigner.
|
|
14
|
+
*/
|
|
15
|
+
export interface GcpKmsSignerOptions {
|
|
16
|
+
/** The version name generated from `client.cryptoKeyVersionPath()` */
|
|
17
|
+
versionName: string;
|
|
18
|
+
/** Options for setting up the GCP KMS client */
|
|
19
|
+
client: KeyManagementServiceClient;
|
|
20
|
+
/** Public key */
|
|
21
|
+
publicKey: PublicKey;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* GCP KMS Signer integrates GCP Key Management Service (KMS) with the Haneul blockchain
|
|
26
|
+
* to provide signing capabilities using GCP-managed cryptographic keys.
|
|
27
|
+
*/
|
|
28
|
+
export class GcpKmsSigner extends Signer {
|
|
29
|
+
#publicKey: PublicKey;
|
|
30
|
+
/** GCP KMS client instance */
|
|
31
|
+
#client: KeyManagementServiceClient;
|
|
32
|
+
/** GCP KMS version name (generated from `client.cryptoKeyVersionPath()`) */
|
|
33
|
+
#versionName: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates an instance of GcpKmsSigner. It's expected to call the static `fromOptions`
|
|
37
|
+
* or `fromVersionName` method to create an instance.
|
|
38
|
+
* For example:
|
|
39
|
+
* ```
|
|
40
|
+
* const signer = await GcpKmsSigner.fromVersionName(versionName);
|
|
41
|
+
* ```
|
|
42
|
+
* @throws Will throw an error if required GCP credentials are not provided.
|
|
43
|
+
*/
|
|
44
|
+
constructor({ versionName, client, publicKey }: GcpKmsSignerOptions) {
|
|
45
|
+
super();
|
|
46
|
+
if (!versionName) throw new Error('Version name is required');
|
|
47
|
+
|
|
48
|
+
this.#client = client;
|
|
49
|
+
this.#versionName = versionName;
|
|
50
|
+
this.#publicKey = publicKey;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Retrieves the key scheme used by this signer.
|
|
55
|
+
* @returns GCP supports only `Secp256k1` and `Secp256r1` schemes.
|
|
56
|
+
*/
|
|
57
|
+
getKeyScheme() {
|
|
58
|
+
return SIGNATURE_FLAG_TO_SCHEME[this.#publicKey.flag() as SignatureFlag];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Retrieves the public key associated with this signer.
|
|
63
|
+
* @returns The Secp256k1PublicKey instance.
|
|
64
|
+
* @throws Will throw an error if the public key has not been initialized.
|
|
65
|
+
*/
|
|
66
|
+
getPublicKey() {
|
|
67
|
+
return this.#publicKey;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Signs the given data using GCP KMS.
|
|
72
|
+
* @param bytes - The data to be signed as a Uint8Array.
|
|
73
|
+
* @returns A promise that resolves to the signature as a Uint8Array.
|
|
74
|
+
* @throws Will throw an error if the public key is not initialized or if signing fails.
|
|
75
|
+
*/
|
|
76
|
+
async sign(bytes: Uint8Array): Promise<Uint8Array<ArrayBuffer>> {
|
|
77
|
+
const [signResponse] = await this.#client.asymmetricSign({
|
|
78
|
+
name: this.#versionName,
|
|
79
|
+
data: bytes,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!signResponse.signature) {
|
|
83
|
+
throw new Error('No signature returned from GCP KMS');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return getConcatenatedSignature(signResponse.signature as Uint8Array, this.getKeyScheme());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Synchronous signing is not supported by GCP KMS.
|
|
91
|
+
* @throws Always throws an error indicating synchronous signing is unsupported.
|
|
92
|
+
* @deprecated use `sign` instead
|
|
93
|
+
*/
|
|
94
|
+
signData(): never {
|
|
95
|
+
throw new Error('GCP Signer does not support sync signing');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Creates a GCP KMS signer from the provided options.
|
|
100
|
+
* Expects the credentials file to be set as an env variable
|
|
101
|
+
* (GOOGLE_APPLICATION_CREDENTIALS).
|
|
102
|
+
*/
|
|
103
|
+
static async fromOptions(options: {
|
|
104
|
+
projectId: string;
|
|
105
|
+
location: string;
|
|
106
|
+
keyRing: string;
|
|
107
|
+
cryptoKey: string;
|
|
108
|
+
cryptoKeyVersion: string;
|
|
109
|
+
}) {
|
|
110
|
+
const client = new KeyManagementServiceClient();
|
|
111
|
+
|
|
112
|
+
const versionName = client.cryptoKeyVersionPath(
|
|
113
|
+
options.projectId,
|
|
114
|
+
options.location,
|
|
115
|
+
options.keyRing,
|
|
116
|
+
options.cryptoKey,
|
|
117
|
+
options.cryptoKeyVersion,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return new GcpKmsSigner({
|
|
121
|
+
versionName,
|
|
122
|
+
client,
|
|
123
|
+
publicKey: await getPublicKey(client, versionName),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static async fromVersionName(versionName: string) {
|
|
128
|
+
const client = new KeyManagementServiceClient();
|
|
129
|
+
return new GcpKmsSigner({
|
|
130
|
+
versionName,
|
|
131
|
+
client,
|
|
132
|
+
publicKey: await getPublicKey(client, versionName),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Retrieves the public key associated with the given version name.
|
|
139
|
+
*/
|
|
140
|
+
async function getPublicKey(
|
|
141
|
+
client: KeyManagementServiceClient,
|
|
142
|
+
versionName: string,
|
|
143
|
+
): Promise<PublicKey> {
|
|
144
|
+
const [publicKey] = await client.getPublicKey({ name: versionName });
|
|
145
|
+
|
|
146
|
+
const { algorithm, pem } = publicKey;
|
|
147
|
+
|
|
148
|
+
if (!pem) throw new Error('No PEM key returned from GCP KMS');
|
|
149
|
+
|
|
150
|
+
const base64 = pem
|
|
151
|
+
.replace('-----BEGIN PUBLIC KEY-----', '')
|
|
152
|
+
.replace('-----END PUBLIC KEY-----', '')
|
|
153
|
+
.replace(/\s/g, '');
|
|
154
|
+
|
|
155
|
+
const compressedKey = publicKeyFromDER(fromBase64(base64));
|
|
156
|
+
|
|
157
|
+
switch (algorithm) {
|
|
158
|
+
case 'EC_SIGN_SECP256K1_SHA256':
|
|
159
|
+
return new Secp256k1PublicKey(compressedKey);
|
|
160
|
+
case 'EC_SIGN_P256_SHA256':
|
|
161
|
+
return new Secp256r1PublicKey(compressedKey);
|
|
162
|
+
default:
|
|
163
|
+
throw new Error(`Unsupported algorithm: ${algorithm}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
package/src/gcp/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Copyright (c) Mysten Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { GcpKmsSignerOptions } from './gcp-kms-client.js';
|
|
5
|
+
import { GcpKmsSigner } from './gcp-kms-client.js';
|
|
6
|
+
|
|
7
|
+
export { GcpKmsSigner };
|
|
8
|
+
|
|
9
|
+
export type { GcpKmsSignerOptions };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Copyright (c) Mysten Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { bcs, TypeTagSerializer } from '@haneullabs/haneul/bcs';
|
|
5
|
+
import type { ObjectOwner } from '@haneullabs/haneul/client';
|
|
6
|
+
import {
|
|
7
|
+
fromBase64,
|
|
8
|
+
normalizeStructTag,
|
|
9
|
+
normalizeHaneulAddress,
|
|
10
|
+
parseStructTag,
|
|
11
|
+
} from '@haneullabs/haneul/utils';
|
|
12
|
+
|
|
13
|
+
const HANEUL_FRAMEWORK_ADDRESS = normalizeHaneulAddress('0x2');
|
|
14
|
+
const HANEUL_SYSTEM_ADDRESS = normalizeHaneulAddress('0x3');
|
|
15
|
+
|
|
16
|
+
const MoveObjectType = bcs.enum('MoveObjectType', {
|
|
17
|
+
Other: bcs.StructTag,
|
|
18
|
+
GasCoin: null,
|
|
19
|
+
StakedSui: null,
|
|
20
|
+
Coin: bcs.TypeTag,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const HaneulMoveObject = bcs.struct('HaneulMoveObject', {
|
|
24
|
+
data: bcs.enum('Data', {
|
|
25
|
+
MoveObject: bcs.struct('MoveObject', {
|
|
26
|
+
type: MoveObjectType.transform({
|
|
27
|
+
input: (objectType: string): typeof MoveObjectType.$inferType => {
|
|
28
|
+
const structTag = parseStructTag(objectType);
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
structTag.address === HANEUL_FRAMEWORK_ADDRESS &&
|
|
32
|
+
structTag.module === 'coin' &&
|
|
33
|
+
structTag.name === 'Coin' &&
|
|
34
|
+
typeof structTag.typeParams[0] === 'object'
|
|
35
|
+
) {
|
|
36
|
+
const innerStructTag = structTag.typeParams[0];
|
|
37
|
+
if (
|
|
38
|
+
innerStructTag.address === HANEUL_FRAMEWORK_ADDRESS &&
|
|
39
|
+
innerStructTag.module === 'haneul' &&
|
|
40
|
+
innerStructTag.name === 'SUI'
|
|
41
|
+
) {
|
|
42
|
+
return { GasCoin: true, $kind: 'GasCoin' };
|
|
43
|
+
}
|
|
44
|
+
return { Coin: normalizeStructTag(innerStructTag), $kind: 'Coin' };
|
|
45
|
+
} else if (
|
|
46
|
+
structTag.address === HANEUL_SYSTEM_ADDRESS &&
|
|
47
|
+
structTag.module === 'staking_pool' &&
|
|
48
|
+
structTag.name === 'StakedSui'
|
|
49
|
+
) {
|
|
50
|
+
return { StakedSui: true, $kind: 'StakedSui' };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
Other: {
|
|
54
|
+
...structTag,
|
|
55
|
+
typeParams: structTag.typeParams.map((typeParam) => {
|
|
56
|
+
return TypeTagSerializer.parseFromStr(normalizeStructTag(typeParam));
|
|
57
|
+
}),
|
|
58
|
+
},
|
|
59
|
+
$kind: 'Other',
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
hasPublicTransfer: bcs.bool(),
|
|
64
|
+
version: bcs.u64(),
|
|
65
|
+
contents: bcs.byteVector().transform({ input: fromBase64 }),
|
|
66
|
+
}),
|
|
67
|
+
}),
|
|
68
|
+
owner: bcs.Owner.transform({
|
|
69
|
+
input: (objectOwner: ObjectOwner) => {
|
|
70
|
+
if (objectOwner === 'Immutable') {
|
|
71
|
+
return { Immutable: null };
|
|
72
|
+
} else if ('Shared' in objectOwner) {
|
|
73
|
+
return { Shared: { initialSharedVersion: objectOwner.Shared.initial_shared_version } };
|
|
74
|
+
} else if ('ConsensusAddressOwner' in objectOwner) {
|
|
75
|
+
return {
|
|
76
|
+
ConsensusAddressOwner: {
|
|
77
|
+
owner: objectOwner.ConsensusAddressOwner.owner,
|
|
78
|
+
startVersion: objectOwner.ConsensusAddressOwner.start_version,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return objectOwner;
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
previousTransaction: bcs.ObjectDigest,
|
|
86
|
+
storageRebate: bcs.u64(),
|
|
87
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Copyright (c) Mysten Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type HaneulLedgerClient from '@haneullabs/ledgerjs-hw-app-haneul';
|
|
5
|
+
import type { HaneulClient } from '@haneullabs/haneul/client';
|
|
6
|
+
import type { SignatureWithBytes } from '@haneullabs/haneul/cryptography';
|
|
7
|
+
import { messageWithIntent, Signer, toSerializedSignature } from '@haneullabs/haneul/cryptography';
|
|
8
|
+
import { Ed25519PublicKey } from '@haneullabs/haneul/keypairs/ed25519';
|
|
9
|
+
import { Transaction } from '@haneullabs/haneul/transactions';
|
|
10
|
+
import { toBase64 } from '@haneullabs/haneul/utils';
|
|
11
|
+
|
|
12
|
+
import { bcs } from '@haneullabs/haneul/bcs';
|
|
13
|
+
import { getInputObjects } from './objects.js';
|
|
14
|
+
import type { Resolution } from '@haneullabs/ledgerjs-hw-app-haneul';
|
|
15
|
+
|
|
16
|
+
export { HaneulMoveObject } from './bcs.js';
|
|
17
|
+
export { getInputObjects } from './objects.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Configuration options for initializing the LedgerSigner.
|
|
21
|
+
*/
|
|
22
|
+
export interface LedgerSignerOptions {
|
|
23
|
+
publicKey: Ed25519PublicKey;
|
|
24
|
+
derivationPath: string;
|
|
25
|
+
ledgerClient: HaneulLedgerClient;
|
|
26
|
+
haneulClient: HaneulClient;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Ledger integrates with the Haneul blockchain to provide signing capabilities using Ledger devices.
|
|
31
|
+
*/
|
|
32
|
+
export class LedgerSigner extends Signer {
|
|
33
|
+
#derivationPath: string;
|
|
34
|
+
#publicKey: Ed25519PublicKey;
|
|
35
|
+
#ledgerClient: HaneulLedgerClient;
|
|
36
|
+
#haneulClient: HaneulClient;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates an instance of LedgerSigner. It's expected to call the static `fromDerivationPath` method to create an instance.
|
|
40
|
+
* @example
|
|
41
|
+
* ```
|
|
42
|
+
* const signer = await LedgerSigner.fromDerivationPath(derivationPath, options);
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
constructor({ publicKey, derivationPath, ledgerClient, haneulClient }: LedgerSignerOptions) {
|
|
46
|
+
super();
|
|
47
|
+
this.#publicKey = publicKey;
|
|
48
|
+
this.#derivationPath = derivationPath;
|
|
49
|
+
this.#ledgerClient = ledgerClient;
|
|
50
|
+
this.#haneulClient = haneulClient;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Retrieves the key scheme used by this signer.
|
|
55
|
+
*/
|
|
56
|
+
override getKeyScheme() {
|
|
57
|
+
return 'ED25519' as const;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Retrieves the public key associated with this signer.
|
|
62
|
+
* @returns The Ed25519PublicKey instance.
|
|
63
|
+
*/
|
|
64
|
+
override getPublicKey() {
|
|
65
|
+
return this.#publicKey;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Signs the provided transaction bytes.
|
|
70
|
+
* @returns The signed transaction bytes and signature.
|
|
71
|
+
*/
|
|
72
|
+
override async signTransaction(
|
|
73
|
+
bytes: Uint8Array,
|
|
74
|
+
bcsObjects?: Uint8Array[],
|
|
75
|
+
resolution?: Resolution,
|
|
76
|
+
): Promise<SignatureWithBytes> {
|
|
77
|
+
const transactionOptions = bcsObjects
|
|
78
|
+
? { bcsObjects }
|
|
79
|
+
: await getInputObjects(Transaction.from(bytes), this.#haneulClient).catch(() => ({
|
|
80
|
+
// Fail gracefully so network errors or serialization issues don't break transaction signing:
|
|
81
|
+
bcsObjects: [],
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
const intentMessage = messageWithIntent('TransactionData', bytes);
|
|
85
|
+
const { signature } = await this.#ledgerClient.signTransaction(
|
|
86
|
+
this.#derivationPath,
|
|
87
|
+
intentMessage,
|
|
88
|
+
transactionOptions,
|
|
89
|
+
resolution,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
bytes: toBase64(bytes),
|
|
94
|
+
signature: toSerializedSignature({
|
|
95
|
+
signature,
|
|
96
|
+
signatureScheme: this.getKeyScheme(),
|
|
97
|
+
publicKey: this.#publicKey,
|
|
98
|
+
}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Signs the provided personal message.
|
|
104
|
+
* @returns The signed message bytes and signature.
|
|
105
|
+
*/
|
|
106
|
+
override async signPersonalMessage(bytes: Uint8Array): Promise<SignatureWithBytes> {
|
|
107
|
+
const intentMessage = messageWithIntent(
|
|
108
|
+
'PersonalMessage',
|
|
109
|
+
bcs.byteVector().serialize(bytes).toBytes(),
|
|
110
|
+
);
|
|
111
|
+
const { signature } = await this.#ledgerClient.signTransaction(
|
|
112
|
+
this.#derivationPath,
|
|
113
|
+
intentMessage,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
bytes: toBase64(bytes),
|
|
118
|
+
signature: toSerializedSignature({
|
|
119
|
+
signature,
|
|
120
|
+
signatureScheme: this.getKeyScheme(),
|
|
121
|
+
publicKey: this.#publicKey,
|
|
122
|
+
}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Prepares the signer by fetching and setting the public key from a Ledger device.
|
|
128
|
+
* It is recommended to initialize an `LedgerSigner` instance using this function.
|
|
129
|
+
* @returns A promise that resolves once a `LedgerSigner` instance is prepared (public key is set).
|
|
130
|
+
*/
|
|
131
|
+
static async fromDerivationPath(
|
|
132
|
+
derivationPath: string,
|
|
133
|
+
ledgerClient: HaneulLedgerClient,
|
|
134
|
+
haneulClient: HaneulClient,
|
|
135
|
+
) {
|
|
136
|
+
const { publicKey } = await ledgerClient.getPublicKey(derivationPath);
|
|
137
|
+
if (!publicKey) {
|
|
138
|
+
throw new Error('Failed to get public key from Ledger.');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return new LedgerSigner({
|
|
142
|
+
derivationPath,
|
|
143
|
+
publicKey: new Ed25519PublicKey(publicKey),
|
|
144
|
+
ledgerClient,
|
|
145
|
+
haneulClient,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Generic signing is not supported by Ledger.
|
|
151
|
+
* @throws Always throws an error indicating generic signing is unsupported.
|
|
152
|
+
*/
|
|
153
|
+
override sign(): never {
|
|
154
|
+
throw new Error('Ledger Signer does not support generic signing.');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Generic signing is not supported by Ledger.
|
|
159
|
+
* @throws Always throws an error indicating generic signing is unsupported.
|
|
160
|
+
*/
|
|
161
|
+
override signWithIntent(): never {
|
|
162
|
+
throw new Error('Ledger Signer does not support generic signing.');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Copyright (c) Mysten Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { Transaction } from '@haneullabs/haneul/transactions';
|
|
5
|
+
import type { HaneulClient } from '@haneullabs/haneul/client';
|
|
6
|
+
import { HaneulMoveObject } from './bcs.js';
|
|
7
|
+
|
|
8
|
+
export const getInputObjects = async (transaction: Transaction, client: HaneulClient) => {
|
|
9
|
+
const data = transaction.getData();
|
|
10
|
+
|
|
11
|
+
const gasObjectIds = data.gasData.payment?.map((object) => object.objectId) ?? [];
|
|
12
|
+
const inputObjectIds = data.inputs
|
|
13
|
+
.map((input) => {
|
|
14
|
+
return input.$kind === 'Object' && input.Object.$kind === 'ImmOrOwnedObject'
|
|
15
|
+
? input.Object.ImmOrOwnedObject.objectId
|
|
16
|
+
: null;
|
|
17
|
+
})
|
|
18
|
+
.filter((objectId): objectId is string => !!objectId);
|
|
19
|
+
|
|
20
|
+
const objects = await client.multiGetObjects({
|
|
21
|
+
ids: [...gasObjectIds, ...inputObjectIds],
|
|
22
|
+
options: {
|
|
23
|
+
showBcs: true,
|
|
24
|
+
showPreviousTransaction: true,
|
|
25
|
+
showStorageRebate: true,
|
|
26
|
+
showOwner: true,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// NOTE: We should probably get rid of this manual serialization logic in favor of using the
|
|
31
|
+
// already serialized object bytes from the GraphQL API once there is more mainstream support
|
|
32
|
+
// for it + we can enforce the transport type on the Haneul client.
|
|
33
|
+
const bcsObjects = objects
|
|
34
|
+
.map((object) => {
|
|
35
|
+
if (object.error || !object.data || object.data.bcs?.dataType !== 'moveObject') {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return HaneulMoveObject.serialize({
|
|
40
|
+
data: {
|
|
41
|
+
MoveObject: {
|
|
42
|
+
type: object.data.bcs.type,
|
|
43
|
+
hasPublicTransfer: object.data.bcs.hasPublicTransfer,
|
|
44
|
+
version: object.data.bcs.version,
|
|
45
|
+
contents: object.data.bcs.bcsBytes,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
owner: object.data.owner!,
|
|
49
|
+
previousTransaction: object.data.previousTransaction!,
|
|
50
|
+
storageRebate: object.data.storageRebate!,
|
|
51
|
+
}).toBytes();
|
|
52
|
+
})
|
|
53
|
+
.filter((bcsBytes): bcsBytes is Uint8Array<ArrayBuffer> => !!bcsBytes);
|
|
54
|
+
|
|
55
|
+
return { bcsObjects };
|
|
56
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Copyright (c) Mysten Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { secp256r1 } from '@noble/curves/p256';
|
|
5
|
+
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
6
|
+
import { ASN1Construction, ASN1TagClass, DERElement } from 'asn1-ts';
|
|
7
|
+
|
|
8
|
+
/** The total number of bits in the DER bit string for the uncompressed public key. */
|
|
9
|
+
export const DER_BIT_STRING_LENGTH = 520;
|
|
10
|
+
|
|
11
|
+
/** The total number of bytes corresponding to the DER bit string length. */
|
|
12
|
+
export const DER_BYTES_LENGTH = DER_BIT_STRING_LENGTH / 8;
|
|
13
|
+
|
|
14
|
+
// Reference Specifications:
|
|
15
|
+
// https://datatracker.ietf.org/doc/html/rfc5480#section-2.2
|
|
16
|
+
// https://www.secg.org/sec1-v2.pdf
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Converts an array of bits into a byte array.
|
|
20
|
+
*
|
|
21
|
+
* @param bitsArray - A `Uint8ClampedArray` representing the bits to convert.
|
|
22
|
+
* @returns A `Uint8Array` containing the corresponding bytes.
|
|
23
|
+
*
|
|
24
|
+
* @throws {Error} If the input array does not have the expected length.
|
|
25
|
+
*/
|
|
26
|
+
function bitsToBytes(bitsArray: Uint8ClampedArray): Uint8Array {
|
|
27
|
+
const bytes = new Uint8Array(DER_BYTES_LENGTH);
|
|
28
|
+
for (let i = 0; i < DER_BIT_STRING_LENGTH; i++) {
|
|
29
|
+
if (bitsArray[i] === 1) {
|
|
30
|
+
bytes[Math.floor(i / 8)] |= 1 << (7 - (i % 8));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return bytes;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function publicKeyFromDER(derBytes: Uint8Array) {
|
|
37
|
+
const encodedData: Uint8Array = derBytes;
|
|
38
|
+
const derElement = new DERElement();
|
|
39
|
+
derElement.fromBytes(encodedData);
|
|
40
|
+
|
|
41
|
+
// Validate the ASN.1 structure of the public key
|
|
42
|
+
if (
|
|
43
|
+
!(
|
|
44
|
+
derElement.tagClass === ASN1TagClass.universal &&
|
|
45
|
+
derElement.construction === ASN1Construction.constructed
|
|
46
|
+
)
|
|
47
|
+
) {
|
|
48
|
+
throw new Error('Unexpected ASN.1 structure');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const components = derElement.components;
|
|
52
|
+
const publicKeyElement = components[1];
|
|
53
|
+
|
|
54
|
+
if (!publicKeyElement) {
|
|
55
|
+
throw new Error('Public Key not found in the DER structure');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return compressPublicKeyClamped(publicKeyElement.bitString);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getConcatenatedSignature(signature: Uint8Array, keyScheme: string) {
|
|
62
|
+
if (!signature || signature.length === 0) {
|
|
63
|
+
throw new Error('Invalid signature');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Initialize a DERElement to parse the DER-encoded signature
|
|
67
|
+
const derElement = new DERElement();
|
|
68
|
+
derElement.fromBytes(signature);
|
|
69
|
+
|
|
70
|
+
const [r, s] = derElement.toJSON() as [string, string];
|
|
71
|
+
|
|
72
|
+
switch (keyScheme) {
|
|
73
|
+
case 'Secp256k1':
|
|
74
|
+
return new secp256k1.Signature(BigInt(r), BigInt(s))
|
|
75
|
+
.normalizeS()
|
|
76
|
+
.toCompactRawBytes() as Uint8Array<ArrayBuffer>;
|
|
77
|
+
case 'Secp256r1':
|
|
78
|
+
return new secp256r1.Signature(BigInt(r), BigInt(s))
|
|
79
|
+
.normalizeS()
|
|
80
|
+
.toCompactRawBytes() as Uint8Array<ArrayBuffer>;
|
|
81
|
+
default:
|
|
82
|
+
throw new Error('Unsupported key scheme');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Compresses an uncompressed public key into its compressed form.
|
|
88
|
+
*
|
|
89
|
+
* The uncompressed key must follow the DER bit string format as specified in [RFC 5480](https://datatracker.ietf.org/doc/html/rfc5480#section-2.2)
|
|
90
|
+
* and [SEC 1: Elliptic Curve Cryptography](https://www.secg.org/sec1-v2.pdf).
|
|
91
|
+
*
|
|
92
|
+
* @param uncompressedKey - A `Uint8ClampedArray` representing the uncompressed public key bits.
|
|
93
|
+
* @returns A `Uint8Array` containing the compressed public key.
|
|
94
|
+
*
|
|
95
|
+
* @throws {Error} If the uncompressed key has an unexpected length or does not start with the expected prefix.
|
|
96
|
+
*/
|
|
97
|
+
export function compressPublicKeyClamped(uncompressedKey: Uint8ClampedArray): Uint8Array {
|
|
98
|
+
if (uncompressedKey.length !== DER_BIT_STRING_LENGTH) {
|
|
99
|
+
throw new Error('Unexpected length for an uncompressed public key');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Convert bits to bytes
|
|
103
|
+
const uncompressedBytes = bitsToBytes(uncompressedKey);
|
|
104
|
+
|
|
105
|
+
// Ensure the public key starts with the standard uncompressed prefix 0x04
|
|
106
|
+
if (uncompressedBytes[0] !== 0x04) {
|
|
107
|
+
throw new Error('Public key does not start with 0x04');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Extract X-Coordinate (skip the first byte, which is the prefix 0x04)
|
|
111
|
+
const xCoord = uncompressedBytes.slice(1, 33);
|
|
112
|
+
|
|
113
|
+
// Determine parity byte for Y coordinate based on the last byte
|
|
114
|
+
const yCoordLastByte = uncompressedBytes[64];
|
|
115
|
+
const parityByte = yCoordLastByte % 2 === 0 ? 0x02 : 0x03;
|
|
116
|
+
|
|
117
|
+
// Return the compressed public key consisting of the parity byte and X-coordinate
|
|
118
|
+
return new Uint8Array([parityByte, ...xCoord]);
|
|
119
|
+
}
|