Skip to content

Commit 93934f1

Browse files
committedJan 24, 2023
poor man's derivation path parser
1 parent 110c7d4 commit 93934f1

4 files changed

+83
-24
lines changed
 

‎web3.bip32.pas

+62-4
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
interface
2828

2929
uses
30+
// Delphi
31+
System.SysUtils,
3032
// web3
33+
web3,
3134
web3.bip39;
3235

3336
type
@@ -36,18 +39,24 @@ interface
3639
end;
3740

3841
IPrivateKey = interface(IPublicKey)
42+
function Data: TBytes;
3943
function NewChildKey(childIdx: UInt32): IPrivateKey;
4044
function PublicKey: IPublicKey;
4145
end;
4246

47+
IMasterKey = interface(IPrivateKey)
48+
function GetChildKey(const path: string): IResult<IPrivateKey>;
49+
end;
50+
4351
// creates a new master extended key
44-
function master(const seed: web3.bip39.TSeed): IPrivateKey;
52+
function master(const seed: web3.bip39.TSeed): IMasterKey;
4553

4654
implementation
4755

4856
uses
4957
// Delphi
50-
System.SysUtils,
58+
System.Character,
59+
System.Classes,
5160
// CryptoLib4Pascal
5261
ClpBigInteger,
5362
ClpBigIntegers,
@@ -163,6 +172,7 @@ TPrivateKey = class(TPublicKey, IPrivateKey)
163172
protected
164173
class function isPrivate: Boolean; override;
165174
public
175+
function Data: TBytes;
166176
function NewChildKey(childIdx: UInt32): IPrivateKey;
167177
function PublicKey: IPublicKey;
168178
end;
@@ -174,6 +184,11 @@ class function TPrivateKey.isPrivate: Boolean;
174184
Result := True;
175185
end;
176186

187+
function TPrivateKey.Data: TBytes;
188+
begin
189+
Result := Self.keyData;
190+
end;
191+
177192
// derives a child key from a given parent
178193
function TPrivateKey.NewChildKey(childIdx: UInt32): IPrivateKey;
179194
begin
@@ -215,10 +230,53 @@ function TPrivateKey.PublicKey: IPublicKey;
215230
);
216231
end;
217232

218-
function master(const seed: web3.bip39.TSeed): IPrivateKey;
233+
type
234+
TMasterKey = class(TPrivateKey, IMasterKey)
235+
public
236+
function GetChildKey(const path: string): IResult<IPrivateKey>;
237+
end;
238+
239+
function TMasterKey.GetChildKey(const path: string): IResult<IPrivateKey>;
240+
begin
241+
const SL = TStringList.Create;
242+
try
243+
SL.Delimiter := '/';
244+
SL.DelimitedText := path;
245+
var K: IPrivateKey := Self;
246+
for var I := 0 to Pred(SL.Count) do
247+
begin
248+
var S := SL[I].Trim;
249+
if (I = 0) and S.Equals('m') then CONTINUE;
250+
var C, E: UInt32; // child, error
251+
Val(S, C, E);
252+
if E <> 0 then
253+
begin
254+
if (S = '') or S[High(S)].IsDigit then
255+
begin
256+
Result := TResult<IPrivateKey>.Err(nil, 'invalid derivation path');
257+
EXIT;
258+
end;
259+
Delete(S, High(S), 1);
260+
Val(S, C, E);
261+
if E <> 0 then
262+
begin
263+
Result := TResult<IPrivateKey>.Err(nil, 'invalid derivation path');
264+
EXIT;
265+
end;
266+
C := C + firstHardenedChild;
267+
end;
268+
K := K.NewChildKey(C);
269+
end;
270+
Result := TResult<IPrivateKey>.Ok(K);
271+
finally
272+
SL.Free;
273+
end;
274+
end;
275+
276+
function master(const seed: web3.bip39.TSeed): IMasterKey;
219277
begin
220278
const digest = hmac_sha512(seed, TConverters.ConvertStringToBytes('Bitcoin seed', TEncoding.UTF8));
221-
Result := TPrivateKey.Create(
279+
Result := TMasterKey.Create(
222280
privateWalletVersion, // version
223281
Copy(digest, 0, 32), // key data
224282
Copy(digest, 32, 32), // chain code

‎web3.bip32.tests.json

+13-13
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,27 @@
55
"pubKey": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
66
"children": [
77
{
8-
"path": 2147483648,
8+
"path": "m/0H",
99
"privKey": "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7",
1010
"pubKey": "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"
1111
},
1212
{
13-
"path": 1,
13+
"path": "m/0H/1",
1414
"privKey": "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs",
1515
"pubKey": "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ"
1616
},
1717
{
18-
"path": 2147483650,
18+
"path": "m/0H/1/2H",
1919
"privKey": "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM",
2020
"pubKey": "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5"
2121
},
2222
{
23-
"path": 2,
23+
"path": "m/0H/1/2H/2",
2424
"privKey": "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334",
2525
"pubKey": "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV"
2626
},
2727
{
28-
"path": 1000000000,
28+
"path": "m/0H/1/2H/2/1000000000",
2929
"privKey": "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76",
3030
"pubKey": "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"
3131
}
@@ -37,27 +37,27 @@
3737
"pubKey": "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB",
3838
"children": [
3939
{
40-
"path": 0,
40+
"path": "m/0",
4141
"privKey": "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
4242
"pubKey": "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"
4343
},
4444
{
45-
"path": 4294967295,
45+
"path": "m/0/2147483647H",
4646
"privKey": "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9",
4747
"pubKey": "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a"
4848
},
4949
{
50-
"path": 1,
50+
"path": "m/0/2147483647H/1",
5151
"privKey": "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef",
5252
"pubKey": "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon"
5353
},
5454
{
55-
"path": 4294967294,
55+
"path": "m/0/2147483647H/1/2147483646H",
5656
"privKey": "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc",
5757
"pubKey": "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"
5858
},
5959
{
60-
"path": 2,
60+
"path": "m/0/2147483647H/1/2147483646H/2",
6161
"privKey": "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j",
6262
"pubKey": "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt"
6363
}
@@ -69,7 +69,7 @@
6969
"pubKey": "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13",
7070
"children": [
7171
{
72-
"path": 2147483648,
72+
"path": "m/0H",
7373
"privKey": "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L",
7474
"pubKey": "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y"
7575
}
@@ -81,12 +81,12 @@
8181
"pubKey": "xpub661MyMwAqRbcGczjuMoRm6dXaLDEhW1u34gKenbeYqAix21mdUKJyuyu5F1rzYGVxyL6tmgBUAEPrEz92mBXjByMRiJdba9wpnN37RLLAXa",
8282
"children": [
8383
{
84-
"path": 2147483648,
84+
"path": "m/0H",
8585
"privKey": "xprv9vB7xEWwNp9kh1wQRfCCQMnZUEG21LpbR9NPCNN1dwhiZkjjeGRnaALmPXCX7SgjFTiCTT6bXes17boXtjq3xLpcDjzEuGLQBM5ohqkao9G",
8686
"pubKey": "xpub69AUMk3qDBi3uW1sXgjCmVjJ2G6WQoYSnNHyzkmdCHEhSZ4tBok37xfFEqHd2AddP56Tqp4o56AePAgCjYdvpW2PU2jbUPFKsav5ut6Ch1m"
8787
},
8888
{
89-
"path": 2147483649,
89+
"path": "m/0H/1H",
9090
"privKey": "xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJeHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1",
9191
"pubKey": "xpub6BJA1jSqiukeaesWfxe6sNK9CCGaujFFSJLomWHprUL9DePQ4JDkM5d88n49sMGJxrhpjazuXYWdMf17C9T5XnxkopaeS7jGk1GyyVziaMt"
9292
}

‎web3.bip32.tests.pas

+8-7
Original file line numberDiff line numberDiff line change
@@ -70,20 +70,21 @@ procedure TTests.TestCase;
7070
for var vector in vectors as TJsonArray do
7171
begin
7272
const seed = '0x' + web3.json.getPropAsStr(vector, 'seed');
73-
var parent := web3.bip32.master(web3.utils.fromHex(seed));
73+
const master = web3.bip32.master(web3.utils.fromHex(seed));
7474
var privKey := web3.json.getPropAsStr(vector, 'privKey');
75-
Assert.AreEqual(parent.ToString, privKey);
75+
Assert.AreEqual(master.ToString, privKey);
7676
var pubKey := web3.json.getPropAsStr(vector, 'pubKey');
77-
Assert.AreEqual(parent.PublicKey.ToString, pubKey);
77+
Assert.AreEqual(master.PublicKey.ToString, pubKey);
7878
const children = web3.json.getPropAsArr(vector, 'children');
7979
for var child in children do
8080
begin
81-
const path = web3.json.getPropAsBigInt(child, 'path');
82-
parent := parent.NewChildKey(path.AsCardinal);
81+
const path = web3.json.getPropAsStr(child, 'path');
82+
const key = master.GetChildKey(path);
83+
if key.IsErr then Assert.Fail(key.Error.Message);
8384
privKey := web3.json.getPropAsStr(child, 'privKey');
84-
Assert.AreEqual(parent.ToString, privKey);
85+
Assert.AreEqual(key.Value.ToString, privKey);
8586
pubKey := web3.json.getPropAsStr(child, 'pubKey');
86-
Assert.AreEqual(parent.PublicKey.ToString, pubKey);
87+
Assert.AreEqual(key.Value.PublicKey.ToString, pubKey);
8788
end;
8889
end;
8990
finally

‎web3.bip32.tests.res

104 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.