|
1 | 1 | import type { PrivateJwk, PublicJwk } from '../types/jose-types.js'; |
2 | 2 |
|
3 | 3 | import * as secp256k1 from '@noble/secp256k1'; |
4 | | -import secp256k1Derivation from 'secp256k1'; |
5 | 4 |
|
6 | 5 | import { Encoder } from '../utils/encoder.js'; |
7 | 6 | import { sha256 } from 'multiformats/hashes/sha2'; |
@@ -157,67 +156,42 @@ export class Secp256k1 { |
157 | 156 | } |
158 | 157 |
|
159 | 158 | /** |
160 | | - * Derives a hierarchical deterministic public key. |
161 | | - * @param key Either a private or an uncompressed public key used to derive the descendant public key. |
| 159 | + * Derives a hardened hierarchical deterministic public key. |
162 | 160 | * @returns uncompressed public key |
163 | 161 | */ |
164 | | - public static async derivePublicKey(key: Uint8Array, relativePath: string[]): Promise<Uint8Array> { |
| 162 | + public static async derivePublicKey(privateKey: Uint8Array, relativePath: string[]): Promise<Uint8Array> { |
165 | 163 | Secp256k1.validateKeyDerivationPath(relativePath); |
166 | 164 |
|
167 | | - let currentPublicKey: Uint8Array; |
168 | | - if (key.length === 32) { |
169 | | - // private key is always 32 bytes |
170 | | - currentPublicKey = secp256k1.getPublicKey(key, false); // `false` = uncompressed |
171 | | - } else { |
172 | | - currentPublicKey = key; |
173 | | - } |
174 | | - |
175 | | - for (const segment of relativePath) { |
176 | | - const hash = await sha256.encode(Encoder.stringToBytes(segment)); |
177 | | - currentPublicKey = Secp256k1.deriveChildPublicKey(currentPublicKey, hash); |
178 | | - } |
179 | | - |
180 | | - return currentPublicKey; |
| 165 | + // derive the private key first then compute the derived public key from the derive private key |
| 166 | + const derivedPrivateKey = await Secp256k1.derivePrivateKey(privateKey, relativePath); |
| 167 | + const derivedPublicKey = await Secp256k1.getPublicKey(derivedPrivateKey); |
| 168 | + return derivedPublicKey; |
181 | 169 | } |
182 | 170 |
|
183 | 171 | /** |
184 | | - * Derives a hierarchical deterministic private key. |
| 172 | + * Derives a hardened hierarchical deterministic private key. |
185 | 173 | */ |
186 | 174 | public static async derivePrivateKey(privateKey: Uint8Array, relativePath: string[]): Promise<Uint8Array> { |
187 | 175 | Secp256k1.validateKeyDerivationPath(relativePath); |
188 | 176 |
|
189 | 177 | let currentPrivateKey = privateKey; |
190 | 178 | for (const segment of relativePath) { |
191 | | - const hash = await sha256.encode(Encoder.stringToBytes(segment)); |
192 | | - currentPrivateKey = Secp256k1.deriveChildPrivateKey(currentPrivateKey, hash); |
| 179 | + const derivationSegment = Encoder.stringToBytes(segment); |
| 180 | + currentPrivateKey = await Secp256k1.deriveChildPrivateKey(currentPrivateKey, derivationSegment); |
193 | 181 | } |
194 | 182 |
|
195 | 183 | return currentPrivateKey; |
196 | 184 | } |
197 | 185 |
|
198 | 186 | /** |
199 | | - * Derives a child public key using the given tweak input. |
200 | | - */ |
201 | | - public static deriveChildPublicKey(uncompressedPublicKey: Uint8Array, tweakInput: Uint8Array): Uint8Array { |
202 | | - // underlying library requires Buffer as input |
203 | | - const compressedPublicKey = false; |
204 | | - const publicKeyBuffer = Buffer.from(uncompressedPublicKey); |
205 | | - const tweakBuffer = Buffer.from(tweakInput); |
206 | | - const derivedPublicKey = secp256k1Derivation.publicKeyTweakAdd(publicKeyBuffer, tweakBuffer, compressedPublicKey); |
207 | | - return derivedPublicKey; |
208 | | - } |
209 | | - |
210 | | - /** |
211 | | - * Derives a child private key using the given tweak input. |
| 187 | + * Derives a child private key using the given derivation path segment. |
212 | 188 | */ |
213 | | - public static deriveChildPrivateKey(privateKey: Uint8Array, tweakInput: Uint8Array): Uint8Array { |
214 | | - // NOTE: passing in private key to v5.0.0 of `secp256k1.privateKeyTweakAdd()` has the side effect of modifying the input private key bytes. |
215 | | - // `secp256k1.publicKeyTweakAdd()` does not have this side effect. |
216 | | - // before there is a fix for it (we can also investigate and submit a PR), cloning the private key to workaround is a MUST |
217 | | - // also underlying library requires Buffer as input |
218 | | - const privateKeyBuffer = Buffer.from(privateKey); |
219 | | - const tweakBuffer = Buffer.from(tweakInput); |
220 | | - const derivedPrivateKey = secp256k1Derivation.privateKeyTweakAdd(privateKeyBuffer, tweakBuffer); |
| 189 | + public static async deriveChildPrivateKey(privateKey: Uint8Array, derivationPathSegment: Uint8Array): Promise<Uint8Array> { |
| 190 | + // hash the private key & derivation segment |
| 191 | + const privateKeyHash = await sha256.encode(privateKey); |
| 192 | + const derivationPathSegmentHash = await sha256.encode(derivationPathSegment); |
| 193 | + const combinedBytes = secp256k1.etc.concatBytes(privateKeyHash, derivationPathSegmentHash); |
| 194 | + const derivedPrivateKey = secp256k1.etc.hashToPrivateKey(combinedBytes); |
221 | 195 | return derivedPrivateKey; |
222 | 196 | } |
223 | 197 |
|
|
0 commit comments