From 09108b2f83b6b3d8af5425ab43ca902bf92081b7 Mon Sep 17 00:00:00 2001 From: Jono Wenger <jono@elixxir.io> Date: Mon, 17 Oct 2022 22:32:38 +0000 Subject: [PATCH] XX-4265 / Construct identity --- go.mod | 4 +- go.sum | 7 +-- indexedDb/implementation.go | 39 +++++++------- indexedDb/implementation_test.go | 43 +++++---------- indexedDb/model.go | 9 +--- wasm/channels.go | 89 +++++++++++++++++++++++++++----- wasm/channels_test.go | 62 ++++++++++++++++++++++ 7 files changed, 176 insertions(+), 77 deletions(-) diff --git a/go.mod b/go.mod index e054815c..bf5e93f4 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ require ( github.com/hack-pad/go-indexeddb v0.2.0 github.com/pkg/errors v0.9.1 github.com/spf13/jwalterweatherman v1.1.0 - gitlab.com/elixxir/client v1.5.1-0.20221017175256-aedcb7c137b7 - gitlab.com/elixxir/crypto v0.0.7-0.20221017173452-565da4101a3b + gitlab.com/elixxir/client v1.5.1-0.20221017222923-185226c4412d + gitlab.com/elixxir/crypto v0.0.7-0.20221017204335-9201b3672f3a gitlab.com/elixxir/primitives v0.0.3-0.20221017172918-6176818d1aba gitlab.com/xx_network/crypto v0.0.5-0.20221017172404-b384a8d8b171 gitlab.com/xx_network/primitives v0.0.4-0.20221017171439-42169a3e5c0d diff --git a/go.sum b/go.sum index ec4e5411..1c0830cd 100644 --- a/go.sum +++ b/go.sum @@ -616,14 +616,15 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f h1:yXGvNBqzZwAhDYlSnxPRbgor6JWoOt1Z7s3z1O9JR40= gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k= -gitlab.com/elixxir/client v1.5.1-0.20221017175256-aedcb7c137b7 h1:NLsGSE/zZwXlMBxzIT7enoj9TRm6jkdfD+2MtsopwSM= -gitlab.com/elixxir/client v1.5.1-0.20221017175256-aedcb7c137b7/go.mod h1:C/BcaLL/hDtub1wj10B8U6Ln6N5S/Q83/zOrE6VIMpE= +gitlab.com/elixxir/client v1.5.1-0.20221017222923-185226c4412d h1:c6hhamrVh5xAG4n/mc4LuPXkPuREd1Ql181wL16i+bI= +gitlab.com/elixxir/client v1.5.1-0.20221017222923-185226c4412d/go.mod h1:/j/GbuxAVfR5cqLqYAq5s8IgafpyHVO63efwh/Xob4w= gitlab.com/elixxir/comms v0.0.4-0.20221017173926-4eaa6061dfaa h1:/FEpu0N0rAyq74FkvO3uY8BcQoWLSbVPhj/s5QfscZw= gitlab.com/elixxir/comms v0.0.4-0.20221017173926-4eaa6061dfaa/go.mod h1:rW7xdbHntP2MoF3q+2+f+IR8OHol94MRyviotfR5rXg= gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c= gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA= -gitlab.com/elixxir/crypto v0.0.7-0.20221017173452-565da4101a3b h1:5VBuenFNKOq0omDYmUet3Deu3pAkzNIkveQBtNO/o0k= gitlab.com/elixxir/crypto v0.0.7-0.20221017173452-565da4101a3b/go.mod h1:1rftbwSVdy49LkBIkPr+w+P2mDOerYeBKoZuB3r0yqI= +gitlab.com/elixxir/crypto v0.0.7-0.20221017204335-9201b3672f3a h1:RxobrpD5owwdyNg5KTqBINJ8z0zsXsbu+UhMEC80wIE= +gitlab.com/elixxir/crypto v0.0.7-0.20221017204335-9201b3672f3a/go.mod h1:1rftbwSVdy49LkBIkPr+w+P2mDOerYeBKoZuB3r0yqI= gitlab.com/elixxir/ekv v0.2.1 h1:dtwbt6KmAXG2Tik5d60iDz2fLhoFBgWwST03p7T+9Is= gitlab.com/elixxir/ekv v0.2.1/go.mod h1:USLD7xeDnuZEavygdrgzNEwZXeLQJK/w1a+htpN+JEU= gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg= diff --git a/indexedDb/implementation.go b/indexedDb/implementation.go index f546a822..b7ac9e54 100644 --- a/indexedDb/implementation.go +++ b/indexedDb/implementation.go @@ -11,6 +11,7 @@ package indexedDb import ( "context" + "crypto/ed25519" "encoding/base64" "encoding/json" "sync" @@ -201,12 +202,13 @@ func (w *wasmModel) deleteMsgByChannel(channelID *id.ID) error { // user of the API to filter such called by message ID. func (w *wasmModel) ReceiveMessage(channelID *id.ID, messageID cryptoChannel.MessageID, nickname, text string, - identity cryptoChannel.Identity, timestamp time.Time, lease time.Duration, - round rounds.Round, mType channels.MessageType, - status channels.SentStatus) uint64 { + pubKey ed25519.PublicKey, codeset uint8, + timestamp time.Time, lease time.Duration, round rounds.Round, + mType channels.MessageType, status channels.SentStatus) uint64 { - msgToInsert := buildMessage(channelID.Marshal(), messageID.Bytes(), nil, - nickname, text, identity, timestamp, lease, round.ID, mType, status) + msgToInsert := buildMessage( + channelID.Marshal(), messageID.Bytes(), nil, nickname, text, pubKey, + codeset, timestamp, lease, round.ID, mType, status) uuid, err := w.receiveHelper(msgToInsert) if err != nil { @@ -225,12 +227,12 @@ func (w *wasmModel) ReceiveMessage(channelID *id.ID, // the initial message. As a result, it may be important to buffer replies. func (w *wasmModel) ReceiveReply(channelID *id.ID, messageID cryptoChannel.MessageID, replyTo cryptoChannel.MessageID, - nickname, text string, identity cryptoChannel.Identity, timestamp time.Time, + nickname, text string, pubKey ed25519.PublicKey, codeset uint8, timestamp time.Time, lease time.Duration, round rounds.Round, mType channels.MessageType, status channels.SentStatus) uint64 { msgToInsert := buildMessage(channelID.Marshal(), messageID.Bytes(), - replyTo.Bytes(), nickname, text, identity, timestamp, lease, round.ID, + replyTo.Bytes(), nickname, text, pubKey, codeset, timestamp, lease, round.ID, mType, status) uuid, err := w.receiveHelper(msgToInsert) @@ -250,13 +252,13 @@ func (w *wasmModel) ReceiveReply(channelID *id.ID, // the initial message. As a result, it may be important to buffer reactions. func (w *wasmModel) ReceiveReaction(channelID *id.ID, messageID cryptoChannel.MessageID, reactionTo cryptoChannel.MessageID, - nickname, reaction string, identity cryptoChannel.Identity, + nickname, reaction string, pubKey ed25519.PublicKey, codeset uint8, timestamp time.Time, lease time.Duration, round rounds.Round, mType channels.MessageType, status channels.SentStatus) uint64 { - msgToInsert := buildMessage(channelID.Marshal(), messageID.Bytes(), - reactionTo.Bytes(), nickname, reaction, identity, timestamp, lease, - round.ID, mType, status) + msgToInsert := buildMessage( + channelID.Marshal(), messageID.Bytes(), reactionTo.Bytes(), nickname, + reaction, pubKey, codeset, timestamp, lease, round.ID, mType, status) uuid, err := w.receiveHelper(msgToInsert) if err != nil { @@ -326,8 +328,8 @@ func (w *wasmModel) UpdateSentStatus(uuid uint64, messageID cryptoChannel.Messag // an existing message, then you need to set it manually // yourself. func buildMessage(channelID, messageID, parentID []byte, nickname, text string, - identity cryptoChannel.Identity, timestamp time.Time, lease time.Duration, - round id.Round, mType channels.MessageType, + pubKey ed25519.PublicKey, codeset uint8, timestamp time.Time, + lease time.Duration, round id.Round, mType channels.MessageType, status channels.SentStatus) *Message { return &Message{ MessageID: messageID, @@ -343,11 +345,8 @@ func buildMessage(channelID, messageID, parentID []byte, nickname, text string, Type: uint16(mType), Round: uint64(round), // User Identity Info - Pubkey: identity.PubKey, - Codename: identity.Codename, - Color: identity.Color, - Extension: identity.Extension, - CodesetVersion: identity.CodesetVersion, + Pubkey: pubKey, + CodesetVersion: codeset, } } @@ -409,9 +408,7 @@ func (w *wasmModel) receiveHelper(newMessage *Message) (uint64, return 0, errors.Errorf("uuid lookup failure: %+v", err) } uuid := uint64(res.Int()) - jww.DEBUG.Printf( - "Successfully stored message from %s, id %d", - newMessage.Codename, uuid) + jww.DEBUG.Printf("Successfully stored message %d", uuid) return uuid, nil } diff --git a/indexedDb/implementation_test.go b/indexedDb/implementation_test.go index 61c55033..8baf354e 100644 --- a/indexedDb/implementation_test.go +++ b/indexedDb/implementation_test.go @@ -12,6 +12,7 @@ package indexedDb import ( "encoding/json" "fmt" + "gitlab.com/xx_network/primitives/netTime" "os" "strconv" "testing" @@ -41,12 +42,10 @@ func TestWasmModel_UpdateSentStatus(t *testing.T) { t.Fatalf("%+v", err) } - cid := channel.Identity{} - // Store a test message testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil, - testString, testString, cid, time.Now(), time.Second, 0, 0, - channels.Sent) + testString, testString, []byte{8, 6, 7, 5}, 0, netTime.Now(), + time.Second, 0, 0, channels.Sent) uuid, err := eventModel.receiveHelper(testMsg) if err != nil { t.Fatalf("%+v", err) @@ -63,7 +62,7 @@ func TestWasmModel_UpdateSentStatus(t *testing.T) { // Update the sentStatus expectedStatus := channels.Failed - eventModel.UpdateSentStatus(uuid, testMsgId, time.Now(), + eventModel.UpdateSentStatus(uuid, testMsgId, netTime.Now(), rounds.Round{ID: 8675309}, expectedStatus) // Check the resulting status @@ -135,14 +134,6 @@ func TestWasmModel_UUIDTest(t *testing.T) { t.Fatalf("%+v", err) } - cid := channel.Identity{ - Codename: "codename123", - PubKey: []byte{8, 6, 7, 5}, - Color: "#FFFFFF", - Extension: "gif", - CodesetVersion: 0, - } - uuids := make([]uint64, 10) for i := 0; i < 10; i++ { @@ -151,9 +142,9 @@ func TestWasmModel_UUIDTest(t *testing.T) { msgID := channel.MessageID{} copy(msgID[:], testString+fmt.Sprintf("%d", i)) rnd := rounds.Round{ID: id.Round(42)} - uuid := eventModel.ReceiveMessage(channelID, msgID, - "test", testString+fmt.Sprintf("%d", i), cid, time.Now(), - time.Hour, rnd, 0, channels.Sent) + uuid := eventModel.ReceiveMessage(channelID, msgID, "test", + testString+fmt.Sprintf("%d", i), []byte{8, 6, 7, 5}, 0, + netTime.Now(), time.Hour, rnd, 0, channels.Sent) uuids[i] = uuid } @@ -178,14 +169,6 @@ func TestWasmModel_DuplicateReceives(t *testing.T) { t.Fatalf("%+v", err) } - cid := channel.Identity{ - Codename: "codename123", - PubKey: []byte{8, 6, 7, 5}, - Color: "#FFFFFF", - Extension: "gif", - CodesetVersion: 0, - } - uuids := make([]uint64, 10) msgID := channel.MessageID{} @@ -194,9 +177,9 @@ func TestWasmModel_DuplicateReceives(t *testing.T) { // Store a test message channelID := id.NewIdFromBytes([]byte(testString), t) rnd := rounds.Round{ID: id.Round(42)} - uuid := eventModel.ReceiveMessage(channelID, msgID, - "test", testString+fmt.Sprintf("%d", i), cid, time.Now(), - time.Hour, rnd, 0, channels.Sent) + uuid := eventModel.ReceiveMessage(channelID, msgID, "test", + testString+fmt.Sprintf("%d", i), []byte{8, 6, 7, 5}, 0, + netTime.Now(), time.Hour, rnd, 0, channels.Sent) uuids[i] = uuid } @@ -228,7 +211,6 @@ func TestWasmModel_deleteMsgByChannel(t *testing.T) { keepChannel := id.NewIdFromString("dontDeleteMe", id.Generic, t) // Store some test messages - cid := channel.Identity{} for i := 0; i < totalMessages; i++ { testStr := testString + strconv.Itoa(i) @@ -239,8 +221,9 @@ func TestWasmModel_deleteMsgByChannel(t *testing.T) { } testMsgId := channel.MakeMessageID([]byte(testStr), &id.ID{1}) - eventModel.ReceiveMessage(thisChannel, testMsgId, testStr, - testStr, cid, time.Now(), time.Second, rounds.Round{ID: id.Round(0)}, 0, channels.Sent) + eventModel.ReceiveMessage(thisChannel, testMsgId, testStr, testStr, + []byte{8, 6, 7, 5}, 0, netTime.Now(), time.Second, + rounds.Round{ID: id.Round(0)}, 0, channels.Sent) } // Check pre-results diff --git a/indexedDb/model.go b/indexedDb/model.go index 0e869277..3500e643 100644 --- a/indexedDb/model.go +++ b/indexedDb/model.go @@ -42,7 +42,6 @@ const ( // // A Message may belong to one Message (Parent). // -// A Message belongs to one User (cryptographic identity). // The user's nickname can change each message, but the rest does not. We // still duplicate all of it for each entry to simplify code for now. type Message struct { @@ -61,13 +60,7 @@ type Message struct { Round uint64 `json:"round"` // User cryptographic Identity struct -- could be pulled out - Pubkey []byte `json:"pubkey"` // Index - // Honorific string `json:"honorific"` - // Adjective string `json:"adjective"` - // Noun string `json:"noun"` - Codename string `json:"codename"` - Color string `json:"color"` - Extension string `json:"extension"` + Pubkey []byte `json:"pubkey"` // Index CodesetVersion uint8 `json:"codeset_version"` } diff --git a/wasm/channels.go b/wasm/channels.go index 7c24054f..317fd9b0 100644 --- a/wasm/channels.go +++ b/wasm/channels.go @@ -10,7 +10,9 @@ package wasm import ( + "encoding/base64" "gitlab.com/xx_network/primitives/id" + "sync" "syscall/js" "gitlab.com/elixxir/client/bindings" @@ -81,7 +83,7 @@ func (ch *ChannelsManager) GetID(js.Value, []js.Value) interface{} { // - args[0] - ID of [Cmix] object in tracker (int). // // Returns: -// - JSON of [channel.PrivateIdentity] (Uint8Array). +// - Marshalled bytes of [channel.PrivateIdentity] (Uint8Array). // - Throws a TypeError if generating the identity fails. func GenerateChannelIdentity(_ js.Value, args []js.Value) interface{} { pi, err := bindings.GenerateChannelIdentity(args[0].Int()) @@ -93,6 +95,64 @@ func GenerateChannelIdentity(_ js.Value, args []js.Value) interface{} { return utils.CopyBytesToJS(pi) } +// identityMap stores identifies previously generated by ConstructIdentity. +var identityMap sync.Map + +// ConstructIdentity constructs a [channel.Identity] from a user's public key +// and codeset version. +// +// Parameters: +// - args[0] - The Ed25519 public key (Uint8Array). +// - args[1] - The version of the codeset used to generate the identity (int). +// +// Returns: +// - JSON of [channel.Identity] (Uint8Array). +// - Throws a TypeError if constructing the identity fails. +func ConstructIdentity(_ js.Value, args []js.Value) interface{} { + // Note: This function is similar to constructIdentity below except that it + // uses a sync.Map backend to increase efficiency for identities that were + // already generated in this browser session. + + pubKey := utils.CopyBytesToGo(args[0]) + pubKeyBase64 := base64.StdEncoding.EncodeToString(pubKey) + identityObj, exists := identityMap.Load(pubKeyBase64) + if exists { + return utils.CopyBytesToJS(identityObj.([]byte)) + } + + identity, err := bindings.ConstructIdentity( + utils.CopyBytesToGo(args[0]), args[1].Int()) + if err != nil { + utils.Throw(utils.TypeError, err) + return nil + } + + identityMap.Store(base64.StdEncoding.EncodeToString(pubKey), identity) + + return utils.CopyBytesToJS(identity) +} + +// constructIdentity constructs a [channel.Identity] from a user's public key +// and codeset version. This function is retain for benchmarking purposes. +// +// Parameters: +// - args[0] - The Ed25519 public key (Uint8Array). +// - args[1] - The version of the codeset used to generate the identity (int). +// +// Returns: +// - JSON of [channel.Identity] (Uint8Array). +// - Throws a TypeError if constructing the identity fails. +func constructIdentity(_ js.Value, args []js.Value) interface{} { + identity, err := bindings.ConstructIdentity( + utils.CopyBytesToGo(args[0]), args[1].Int()) + if err != nil { + utils.Throw(utils.TypeError, err) + return nil + } + + return utils.CopyBytesToJS(identity) +} + // ImportPrivateIdentity generates a new [channel.PrivateIdentity] from exported // data. // @@ -1034,7 +1094,8 @@ func (em *eventModel) LeaveChannel(channelID []byte) { // (Uint8Array). // - nickname - The nickname of the sender of the message (string). // - text - The content of the message (string). -// - identity - JSON of the sender's public ([channel.Identity]) (Uint8Array). +// - pubKey - The sender's Ed25519 public key (Uint8Array). +// - codeset - The codeset version (int). // - timestamp - Time the message was received; represented as nanoseconds // since unix epoch (int). // - lease - The number of nanoseconds that the message is valid for (int). @@ -1051,12 +1112,12 @@ func (em *eventModel) LeaveChannel(channelID []byte) { // - A non-negative unique UUID for the message that it can be referenced by // later with [eventModel.UpdateSentStatus]. func (em *eventModel) ReceiveMessage(channelID, messageID []byte, nickname, - text string, identity []byte, timestamp, lease, roundId, msgType, + text string, pubKey []byte, codeset int, timestamp, lease, roundId, msgType, status int64) int64 { uuid := em.receiveMessage(utils.CopyBytesToJS(channelID), utils.CopyBytesToJS(messageID), nickname, text, - utils.CopyBytesToJS(identity), - timestamp, lease, roundId, msgType, status) + utils.CopyBytesToJS(pubKey), codeset, timestamp, lease, roundId, + msgType, status) return int64(uuid.Int()) } @@ -1076,7 +1137,8 @@ func (em *eventModel) ReceiveMessage(channelID, messageID []byte, nickname, // (Uint8Array). // - senderUsername - The username of the sender of the message (string). // - text - The content of the message (string). -// - identity - JSON of the sender's public ([channel.Identity]) (Uint8Array). +// - pubKey - The sender's Ed25519 public key (Uint8Array). +// - codeset - The codeset version (int). // - timestamp - Time the message was received; represented as nanoseconds // since unix epoch (int). // - lease - The number of nanoseconds that the message is valid for (int). @@ -1093,11 +1155,11 @@ func (em *eventModel) ReceiveMessage(channelID, messageID []byte, nickname, // - A non-negative unique UUID for the message that it can be referenced by // later with [eventModel.UpdateSentStatus]. func (em *eventModel) ReceiveReply(channelID, messageID, reactionTo []byte, - senderUsername, text string, identity []byte, timestamp, lease, roundId, - msgType, status int64) int64 { + senderUsername, text string, pubKey []byte, codeset int, timestamp, lease, + roundId, msgType, status int64) int64 { uuid := em.receiveReply(utils.CopyBytesToJS(channelID), utils.CopyBytesToJS(messageID), utils.CopyBytesToJS(reactionTo), - senderUsername, text, utils.CopyBytesToJS(identity), + senderUsername, text, utils.CopyBytesToJS(pubKey), codeset, timestamp, lease, roundId, msgType, status) return int64(uuid.Int()) @@ -1118,7 +1180,8 @@ func (em *eventModel) ReceiveReply(channelID, messageID, reactionTo []byte, // (Uint8Array). // - senderUsername - The username of the sender of the message (string). // - reaction - The contents of the reaction message (string). -// - identity - JSON of the sender's public ([channel.Identity]) (Uint8Array). +// - pubKey - The sender's Ed25519 public key (Uint8Array). +// - codeset - The codeset version (int). // - timestamp - Time the message was received; represented as nanoseconds // since unix epoch (int). // - lease - The number of nanoseconds that the message is valid for (int). @@ -1135,11 +1198,11 @@ func (em *eventModel) ReceiveReply(channelID, messageID, reactionTo []byte, // - A non-negative unique UUID for the message that it can be referenced by // later with [eventModel.UpdateSentStatus]. func (em *eventModel) ReceiveReaction(channelID, messageID, reactionTo []byte, - senderUsername, reaction string, identity []byte, timestamp, lease, roundId, - msgType, status int64) int64 { + senderUsername, reaction string, pubKey []byte, codeset int, timestamp, + lease, roundId, msgType, status int64) int64 { uuid := em.receiveReaction(utils.CopyBytesToJS(channelID), utils.CopyBytesToJS(messageID), utils.CopyBytesToJS(reactionTo), - senderUsername, reaction, utils.CopyBytesToJS(identity), + senderUsername, reaction, utils.CopyBytesToJS(pubKey), codeset, timestamp, lease, roundId, msgType, status) return int64(uuid.Int()) diff --git a/wasm/channels_test.go b/wasm/channels_test.go index c9ec47fb..2e19cffd 100644 --- a/wasm/channels_test.go +++ b/wasm/channels_test.go @@ -11,7 +11,11 @@ package wasm import ( "gitlab.com/elixxir/client/bindings" + "gitlab.com/elixxir/crypto/channel" + "gitlab.com/elixxir/xxdk-wasm/utils" + "gitlab.com/xx_network/crypto/csprng" "reflect" + "syscall/js" "testing" ) @@ -55,3 +59,61 @@ func Test_ChannelsManagerMethods(t *testing.T) { } } } + +type jsIdentity struct { + pubKey js.Value + codeset js.Value +} + +// Benchmark times the ConstructIdentity, which uses a sync.Map to increase +// efficiency for previously generated identities. +func BenchmarkConstructIdentity(b *testing.B) { + const n = 100_000 + identities, j := make([]jsIdentity, 1000), 0 + for i := 0; i < n; i++ { + pi, err := channel.GenerateIdentity(csprng.NewSystemRNG()) + if err != nil { + b.Fatalf("%+v", err) + } + + pubKey := utils.CopyBytesToJS(pi.PubKey) + codeset := js.ValueOf(int(pi.CodesetVersion)) + ConstructIdentity(js.Value{}, []js.Value{pubKey, codeset}) + + if i%(n/len(identities)) == 0 { + identities[j] = jsIdentity{pubKey, codeset} + j++ + } + } + + b.ResetTimer() + for i := range identities { + go func(identity jsIdentity) { + ConstructIdentity( + js.Value{}, []js.Value{identity.pubKey, identity.codeset}) + }(identities[i]) + } +} + +// Benchmark times the constructIdentity, which generates each new identity. +func Benchmark_constructIdentity(b *testing.B) { + identities := make([]jsIdentity, b.N) + for i := range identities { + pi, err := channel.GenerateIdentity(csprng.NewSystemRNG()) + if err != nil { + b.Fatalf("%+v", err) + } + + pubKey := utils.CopyBytesToJS(pi.PubKey) + codeset := js.ValueOf(int(pi.CodesetVersion)) + identities[i] = jsIdentity{pubKey, codeset} + } + + b.ResetTimer() + for i := range identities { + go func(identity jsIdentity) { + constructIdentity( + js.Value{}, []js.Value{identity.pubKey, identity.codeset}) + }(identities[i]) + } +} -- GitLab