This repository has been archived by the owner on Jan 22, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 924
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(experimental): a sham for Keypair
- Loading branch information
1 parent
d0504a4
commit 69ba06a
Showing
7 changed files
with
176 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
packages/library-legacy-sham/src/__tests__/key-pair-test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { utils } from '@noble/ed25519'; | ||
|
||
import { Keypair } from '../key-pair'; | ||
import { PublicKey } from '../public-key'; | ||
|
||
const MOCK_PRIVATE_KEY_BYTES = [ | ||
151, 227, 37, 180, 104, 169, 5, 53, 191, 115, 132, 187, 223, 228, 25, 52, 7, 50, 86, 18, 151, 45, 105, 68, 31, 21, | ||
128, 21, 32, 16, 222, 239, | ||
]; | ||
const MOCK_PUBLIC_KEY_BYTES = [ | ||
117, 62, 75, 185, 26, 65, 209, 23, 95, 56, 97, 216, 197, 215, 208, 14, 138, 142, 59, 114, 43, 60, 190, 86, 21, 58, | ||
46, 232, 77, 145, 46, 101, | ||
]; | ||
|
||
describe('KeypairSham', () => { | ||
it.each(['fromSecretKey', 'fromSeed'] as (keyof typeof Keypair)[])('throws when calling `%s`', method => { | ||
expect(() => | ||
// This is basically just complaining that `prototype` is not callable. | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
Keypair[method]() | ||
).toThrow(`Keypair#${method.toString()} is unimplemented`); | ||
}); | ||
describe('generate()', () => { | ||
it('returns a `Keypair` instance', () => { | ||
expect(Keypair.generate()).toBeInstanceOf(Keypair); | ||
}); | ||
}); | ||
describe.each([ | ||
[ | ||
'generated keypair', | ||
() => { | ||
jest.spyOn(utils, 'randomPrivateKey').mockReturnValue(new Uint8Array(MOCK_PRIVATE_KEY_BYTES)); | ||
return new Keypair(); | ||
}, | ||
], | ||
[ | ||
'user-supplied keypair', | ||
() => | ||
new Keypair({ | ||
publicKey: new Uint8Array(MOCK_PUBLIC_KEY_BYTES), | ||
secretKey: new Uint8Array([...MOCK_PRIVATE_KEY_BYTES, ...MOCK_PUBLIC_KEY_BYTES]), | ||
}), | ||
], | ||
[ | ||
'user-supplied keypair whose `publicKey` does not correspond to the supplied private key', | ||
() => | ||
new Keypair({ | ||
publicKey: new Uint8Array(Array(32).fill(9)), | ||
secretKey: new Uint8Array([...MOCK_PRIVATE_KEY_BYTES, ...MOCK_PUBLIC_KEY_BYTES]), | ||
}), | ||
], | ||
[ | ||
"user-supplied keypair whose last 32 bytes of the `secretKey` do not represent the private key's public key", | ||
() => | ||
new Keypair({ | ||
publicKey: new Uint8Array(MOCK_PUBLIC_KEY_BYTES), | ||
secretKey: new Uint8Array([...MOCK_PRIVATE_KEY_BYTES, ...Array(32).fill(9)]), | ||
}), | ||
], | ||
])('given a %s', (_, createKeyPair) => { | ||
let keyPair: Keypair; | ||
beforeEach(() => { | ||
keyPair = createKeyPair(); | ||
}); | ||
it('vends the a public key instance at `publicKey`', () => { | ||
expect(keyPair.publicKey).toBeInstanceOf(PublicKey); | ||
}); | ||
it('vends the public key associated with the secret key', () => { | ||
expect(keyPair.publicKey.toBytes()).toEqual(new Uint8Array(MOCK_PUBLIC_KEY_BYTES)); | ||
}); | ||
it('vends a 64 byte array at `secretKey`, the first half of which is the private key and the second half which is the public key', () => { | ||
expect(keyPair.secretKey).toEqual(new Uint8Array([...MOCK_PRIVATE_KEY_BYTES, ...MOCK_PUBLIC_KEY_BYTES])); | ||
}); | ||
it('throws when accessing `_keypair`', () => { | ||
expect(() => { | ||
keyPair._keypair; | ||
}).toThrow(); | ||
}); | ||
}); | ||
}); |
17 changes: 17 additions & 0 deletions
17
packages/library-legacy-sham/src/__typetests__/key-pair-typetest.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* eslint-disable @typescript-eslint/ban-ts-comment */ | ||
import { Keypair as LegacyKeypairWithPrivateKeypairProperty } from '@solana/web3.js-legacy'; | ||
|
||
import { Keypair } from '../key-pair'; | ||
|
||
type LegacyKeypair = Omit<LegacyKeypairWithPrivateKeypairProperty, '_keypair'>; | ||
|
||
new Keypair() satisfies LegacyKeypair; | ||
new Keypair({ | ||
publicKey: new Uint8Array([]), | ||
secretKey: new Uint8Array([]), | ||
}) satisfies LegacyKeypair; | ||
|
||
// I want this to pass, but there's no way to match the `_keypair` properties | ||
// in each of these classes, because the legacy one has `private` visibilty. | ||
// @ts-expect-error | ||
Keypair satisfies typeof LegacyKeypairWithPrivateKeypairProperty; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { etc, getPublicKey, utils } from '@noble/ed25519'; | ||
import { sha512 } from '@noble/hashes/sha512'; | ||
import { getAddressDecoder } from '@solana/addresses'; | ||
|
||
import { PublicKey } from './public-key'; | ||
import { createUnimplementedFunction } from './unimplemented'; | ||
|
||
export class Keypair { | ||
#cachedPublicKey: PublicKey | undefined; | ||
#cachedPublicKeyBytes: Uint8Array | undefined; | ||
#secretKeyBytes: Uint8Array; | ||
constructor(keypair?: { | ||
publicKey: Uint8Array; | ||
/** | ||
* A 64 byte secret key, the first 32 bytes of which is the | ||
* private scalar and the last 32 bytes is the public key. | ||
* Read more: https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/ | ||
*/ | ||
secretKey: Uint8Array; | ||
}) { | ||
if (keypair) { | ||
this.#secretKeyBytes = keypair.secretKey.slice(0, 32); | ||
} else { | ||
this.#secretKeyBytes = this.#generateSecretKeyBytes(); | ||
} | ||
} | ||
get #publicKeyBytes() { | ||
if (!this.#cachedPublicKeyBytes) { | ||
if (!etc.sha512Sync) { | ||
etc.sha512Sync = (...m) => sha512(etc.concatBytes(...m)); | ||
} | ||
this.#cachedPublicKeyBytes = getPublicKey(this.#secretKeyBytes); | ||
} | ||
return this.#cachedPublicKeyBytes; | ||
} | ||
#generateSecretKeyBytes() { | ||
return utils.randomPrivateKey(); | ||
} | ||
get _keypair() { | ||
throw new Error( | ||
'This error is being thrown from `@solana/web3.js-legacy-sham`. The legacy ' + | ||
'implementation of `Keypair` historically exposed the internal property ' + | ||
'`_keypair` but the sham does not. Please eliminate this access of `_keypair` ' + | ||
'and replace it with an implementation that makes use of the available public ' + | ||
'methods.' | ||
); | ||
} | ||
get publicKey(): PublicKey { | ||
if (!this.#cachedPublicKey) { | ||
const publicKeyBytes = this.#publicKeyBytes; | ||
const [address] = getAddressDecoder().decode(publicKeyBytes); | ||
this.#cachedPublicKey = new PublicKey(address); | ||
} | ||
return this.#cachedPublicKey; | ||
} | ||
get secretKey(): Uint8Array { | ||
return new Uint8Array([...this.#secretKeyBytes, ...this.#publicKeyBytes]); | ||
} | ||
static fromSecretKey = createUnimplementedFunction('Keypair#fromSecretKey'); | ||
static fromSeed = createUnimplementedFunction('Keypair#fromSeed'); | ||
static generate() { | ||
return new Keypair(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.