Skip to content

Commit 5caa7e7

Browse files
committed
Fix and validate public key derivation
1 parent 554d18e commit 5caa7e7

File tree

3 files changed

+127
-123
lines changed

3 files changed

+127
-123
lines changed

src/derivers/bip32.test.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ describe('deriveChildKey', () => {
5151
`);
5252
});
5353

54-
it.each(fixtures.bip32InvalidPrivateKeys.keys)(
54+
it.each(fixtures.errorHandling.bip32.keys)(
5555
'handles invalid keys using BIP-32 (test vectors)',
5656
async ({ path, privateKey, chainCode, index, depth }) => {
5757
const node = await createBip39KeyFromSeed(
58-
hexToBytes(fixtures.bip32InvalidPrivateKeys.hexSeed),
58+
hexToBytes(fixtures.errorHandling.bip32.hexSeed),
5959
secp256k1,
6060
);
6161

@@ -103,11 +103,11 @@ describe('deriveChildKey', () => {
103103
`);
104104
});
105105

106-
it.each(fixtures.slip10InvalidPrivateKeys.keys)(
106+
it.each(fixtures.errorHandling.slip10.keys)(
107107
'handles invalid keys using SLIP-10 (test vectors)',
108108
async ({ path, privateKey, chainCode }) => {
109109
const node = await createBip39KeyFromSeed(
110-
hexToBytes(fixtures.slip10InvalidPrivateKeys.hexSeed),
110+
hexToBytes(fixtures.errorHandling.slip10.hexSeed),
111111
secp256k1,
112112
'slip10',
113113
);
@@ -145,8 +145,6 @@ describe('deriveChildKey', () => {
145145

146146
describe('public key derivation', () => {
147147
it('handles invalid keys using BIP-32', async () => {
148-
// TODO: Compare these results to reference implementations.
149-
150148
const node = await SLIP10Node.fromDerivationPath({
151149
derivationPath: [bip39MnemonicToMultipath(fixtures.local.mnemonic)],
152150
curve: 'secp256k1',
@@ -168,22 +166,20 @@ describe('deriveChildKey', () => {
168166
expect(childNode.index).toBe(1);
169167
expect(childNode).toMatchInlineSnapshot(`
170168
Object {
171-
"chainCode": "0x145e1cca88eec3c355707226a1fb87a91e27e18720d9d91c6555cfa307207dbe",
169+
"chainCode": "0x4304d9e48a694baabefba498c2ef85f9e88307f4f621f79f19cbf5f704483130",
172170
"curve": "secp256k1",
173171
"depth": 1,
174172
"index": 1,
175173
"masterFingerprint": 3293725253,
176174
"parentFingerprint": 3293725253,
177175
"privateKey": undefined,
178-
"publicKey": "0x047e2329db2561e463f4acaefec0d1c89452592ab393552aaa2f3d1afcc93e03ba51fe19e3d994c4c00cc438fa33af2ba5dc41a7d969c6dcab0724411f52a6ade8",
176+
"publicKey": "0x048aa5d3fe38c7e81685f9efa72d8b4e9f2cb61647c954e9cdf324a6eefe8a4a00c2b7fa2b2d3e598c87d244fb4eb8708e402aa5ccd945533f4a6ddbc026f77c7b",
179177
"specification": "bip32",
180178
}
181179
`);
182180
});
183181

184182
it('handles invalid keys using SLIP-10', async () => {
185-
// TODO: Compare these results to reference implementations.
186-
187183
const node = await SLIP10Node.fromDerivationPath({
188184
derivationPath: [bip39MnemonicToMultipath(fixtures.local.mnemonic)],
189185
curve: 'secp256k1',

src/derivers/bip32.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,10 +376,20 @@ async function derivePublicChildKey({
376376
case 'bip32': {
377377
validateBIP32Index(childIndex + 1);
378378

379+
const publicExtension = derivePublicExtension({
380+
parentPublicKey: publicKey,
381+
childIndex: childIndex + 1,
382+
});
383+
384+
const newEntropy = generateEntropy({
385+
chainCode,
386+
extension: publicExtension,
387+
});
388+
379389
// As per BIP-32, if the resulting key is invalid, the key is generated
380390
// from the next child index instead.
381391
return await derivePublicChildKey({
382-
entropy,
392+
entropy: newEntropy,
383393
publicKey,
384394
chainCode,
385395
depth,

test/fixtures.ts

Lines changed: 110 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -777,134 +777,132 @@ export default {
777777
// these values are generated using mock return values: The first call to
778778
// `isValidPrivateKey` returns `false`, and any subsequent calls return
779779
// `true`.
780-
bip32InvalidPrivateKeys: {
781-
hexSeed:
782-
'0x747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03',
783-
keys: [
784-
{
785-
path: {
786-
ours: {
787-
tuple: [`bip32:3`],
788-
string: `bip32:3`,
780+
errorHandling: {
781+
bip32: {
782+
hexSeed:
783+
'0x747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03',
784+
keys: [
785+
{
786+
path: {
787+
ours: {
788+
tuple: [`bip32:3`],
789+
string: `bip32:3`,
790+
},
791+
theirs: `m/3`,
789792
},
790-
theirs: `m/3`,
793+
privateKey:
794+
'0x4f1502ee43a67a8eae936df0eb9fd7c4cc11c4c4d76cdeb4efb2711319636500',
795+
chainCode:
796+
'0x576063b42e8e274ceb0a4d5aa2d6eac96334c526634daa7aaf0b44775a08e54e',
797+
depth: 1,
798+
index: 4,
791799
},
792-
privateKey:
793-
'0x4f1502ee43a67a8eae936df0eb9fd7c4cc11c4c4d76cdeb4efb2711319636500',
794-
chainCode:
795-
'0x576063b42e8e274ceb0a4d5aa2d6eac96334c526634daa7aaf0b44775a08e54e',
796-
depth: 1,
797-
index: 4,
798-
},
799-
{
800-
path: {
801-
ours: {
802-
tuple: [`bip32:3`, `bip32:0`],
803-
string: `bip32:3/bip32:0`,
800+
{
801+
path: {
802+
ours: {
803+
tuple: [`bip32:3`, `bip32:0`],
804+
string: `bip32:3/bip32:0`,
805+
},
806+
theirs: `m/3/0`,
804807
},
805-
theirs: `m/3/0`,
808+
privateKey:
809+
'0xc54cdf5ee9a8504000b09334c5085513a380f6b30bb5ea5a944bc44e8a720890',
810+
chainCode:
811+
'0x9018faed4ffcfd70528920a78aef034c76a5c65d9fee0b85ab9d93af2f650b8f',
812+
depth: 2,
813+
index: 0,
806814
},
807-
privateKey:
808-
'0xc54cdf5ee9a8504000b09334c5085513a380f6b30bb5ea5a944bc44e8a720890',
809-
chainCode:
810-
'0x9018faed4ffcfd70528920a78aef034c76a5c65d9fee0b85ab9d93af2f650b8f',
811-
depth: 2,
812-
index: 0,
813-
},
814-
{
815-
path: {
816-
ours: {
817-
tuple: [`bip32:123'`],
818-
string: `bip32:123'`,
815+
{
816+
path: {
817+
ours: {
818+
tuple: [`bip32:123'`],
819+
string: `bip32:123'`,
820+
},
821+
theirs: `m/123'`,
819822
},
820-
theirs: `m/123'`,
823+
privateKey:
824+
'0x0b2cb2b767417eb0c62c1d9de3d3d0e45c4136747cf18d70c29adefcc0556a6d',
825+
chainCode:
826+
'0xb036a60d0615c1f693c0fe1a6fc62ba2b089c964a809a10aa2c4c08d59bb06a2',
827+
depth: 1,
828+
index: 0x80000000 + 124,
821829
},
822-
privateKey:
823-
'0x0b2cb2b767417eb0c62c1d9de3d3d0e45c4136747cf18d70c29adefcc0556a6d',
824-
chainCode:
825-
'0xb036a60d0615c1f693c0fe1a6fc62ba2b089c964a809a10aa2c4c08d59bb06a2',
826-
depth: 1,
827-
index: 0x80000000 + 124,
828-
},
829-
{
830-
path: {
831-
ours: {
832-
tuple: [`bip32:123'`, `bip32:456'`],
833-
string: `bip32:123'/bip32:456'`,
830+
{
831+
path: {
832+
ours: {
833+
tuple: [`bip32:123'`, `bip32:456'`],
834+
string: `bip32:123'/bip32:456'`,
835+
},
836+
theirs: `m/123'/456'`,
834837
},
835-
theirs: `m/123'/456'`,
838+
privateKey:
839+
'0x2915b5e7d9eecf028c96400e0490e4ae5e12b16426304810d69d8cccee529c89',
840+
chainCode:
841+
'0x0a1db8b483d43605120241c81259ea1f64b75c59a3fe5427186ac221cbe3f4d8',
842+
depth: 2,
843+
index: 0x80000000 + 456,
836844
},
837-
privateKey:
838-
'0x2915b5e7d9eecf028c96400e0490e4ae5e12b16426304810d69d8cccee529c89',
839-
chainCode:
840-
'0x0a1db8b483d43605120241c81259ea1f64b75c59a3fe5427186ac221cbe3f4d8',
841-
depth: 2,
842-
index: 0x80000000 + 456,
843-
},
844-
],
845-
},
845+
],
846+
},
846847

847-
// These vectors test the error handling behaviour, when an invalid private
848-
// key is derived. The actual chance of this happening is extremely low, so
849-
// these values are generated using mock return values: The first call to
850-
// `isValidPrivateKey` returns `false`, and any subsequent calls return
851-
// `true`.
852-
slip10InvalidPrivateKeys: {
853-
hexSeed:
854-
'0x747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03',
855-
keys: [
856-
{
857-
path: {
858-
ours: {
859-
tuple: [`bip32:3`],
860-
string: `bip32:3`,
848+
// Note that this uses the `secp256k1` curve.
849+
slip10: {
850+
hexSeed:
851+
'0x747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03',
852+
keys: [
853+
{
854+
path: {
855+
ours: {
856+
tuple: [`bip32:3`],
857+
string: `bip32:3`,
858+
},
859+
theirs: `m/3`,
861860
},
862-
theirs: `m/3`,
861+
privateKey:
862+
'0x7f861ec23cea91757c58ecd4a17dcb9396b3adfdf6a7a97395746fdb5c56854f',
863+
chainCode:
864+
'0xa801ebec457c8ea6c50dc55bea849384a40c92159ad1972f1a35cc92e145c827',
863865
},
864-
privateKey:
865-
'0x7f861ec23cea91757c58ecd4a17dcb9396b3adfdf6a7a97395746fdb5c56854f',
866-
chainCode:
867-
'0xa801ebec457c8ea6c50dc55bea849384a40c92159ad1972f1a35cc92e145c827',
868-
},
869-
{
870-
path: {
871-
ours: {
872-
tuple: [`bip32:3`, `bip32:0`],
873-
string: `bip32:3/bip32:0`,
866+
{
867+
path: {
868+
ours: {
869+
tuple: [`bip32:3`, `bip32:0`],
870+
string: `bip32:3/bip32:0`,
871+
},
872+
theirs: `m/3/0`,
874873
},
875-
theirs: `m/3/0`,
874+
privateKey:
875+
'0x1657de87ad376e58538e72fe30a7567ddc0a7eef08ee93a6cdcf7c83f4a62786',
876+
chainCode:
877+
'0x8235d18356fe4ad0f5bd99ef69c57613e871a66aeb3a9abfdd986188858f41f6',
876878
},
877-
privateKey:
878-
'0x1657de87ad376e58538e72fe30a7567ddc0a7eef08ee93a6cdcf7c83f4a62786',
879-
chainCode:
880-
'0x8235d18356fe4ad0f5bd99ef69c57613e871a66aeb3a9abfdd986188858f41f6',
881-
},
882-
{
883-
path: {
884-
ours: {
885-
tuple: [`bip32:123'`],
886-
string: `bip32:123'`,
879+
{
880+
path: {
881+
ours: {
882+
tuple: [`bip32:123'`],
883+
string: `bip32:123'`,
884+
},
885+
theirs: `m/123'`,
887886
},
888-
theirs: `m/123'`,
887+
privateKey:
888+
'0x3967b87fd18ac7cbba8f587ad558070d30b60ba0371e69deb779115001614717',
889+
chainCode:
890+
'0x7ea274d541b03eef655ba0dce49f80d3618c1206cdc99da662f32b1193225829',
889891
},
890-
privateKey:
891-
'0x3967b87fd18ac7cbba8f587ad558070d30b60ba0371e69deb779115001614717',
892-
chainCode:
893-
'0x7ea274d541b03eef655ba0dce49f80d3618c1206cdc99da662f32b1193225829',
894-
},
895-
{
896-
path: {
897-
ours: {
898-
tuple: [`bip32:123'`, `bip32:456'`],
899-
string: `bip32:123'/bip32:456'`,
892+
{
893+
path: {
894+
ours: {
895+
tuple: [`bip32:123'`, `bip32:456'`],
896+
string: `bip32:123'/bip32:456'`,
897+
},
898+
theirs: `m/123'/456'`,
900899
},
901-
theirs: `m/123'/456'`,
900+
privateKey:
901+
'0xe533b68958c076ce6d42420d61695d83a0a1af24a9ae5564b7679f52d6dd6f5e',
902+
chainCode:
903+
'0xfbd82ae4ac9f4e8176034b54a7ed5a2b836879bb1f94b136890fc86fcf4cd4c5',
902904
},
903-
privateKey:
904-
'0xe533b68958c076ce6d42420d61695d83a0a1af24a9ae5564b7679f52d6dd6f5e',
905-
chainCode:
906-
'0xfbd82ae4ac9f4e8176034b54a7ed5a2b836879bb1f94b136890fc86fcf4cd4c5',
907-
},
908-
],
905+
],
906+
},
909907
},
910908
} as const;

0 commit comments

Comments
 (0)