Skip to content

Commit b2e5ec5

Browse files
authored
feat: adds offchain/ocr package a method to generate BIP39 mnemonic phrases (#310)
This change adds a method to generate BIP39 mnemonic phrases to the offchain/ocr package. These phrases are used for OCR Secrets.
1 parent 869408b commit b2e5ec5

File tree

4 files changed

+177
-0
lines changed

4 files changed

+177
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/avast/retry-go/v4 v4.6.1
1111
github.com/aws/aws-sdk-go v1.55.7
1212
github.com/block-vision/sui-go-sdk v1.0.6
13+
github.com/cosmos/go-bip39 v1.0.0
1314
github.com/ethereum/go-ethereum v1.15.7
1415
github.com/fbsobreira/gotron-sdk v0.0.0-20250403083053-2943ce8c759b
1516
github.com/gagliardetto/solana-go v1.13.0

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLV
157157
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
158158
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
159159
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
160+
github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY=
161+
github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=
160162
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
161163
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
162164
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
@@ -712,6 +714,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
712714
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
713715
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
714716
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
717+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
715718
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
716719
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
717720
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
@@ -851,6 +854,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
851854
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
852855
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
853856
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
857+
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
854858
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
855859
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
856860
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=

offchain/ocr/ocr.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package ocr
2+
3+
import (
4+
"github.com/cosmos/go-bip39"
5+
)
6+
7+
// NewBIP39Mnemonic generates a new BIP39 mnemonic phrase with the specified entropy size.
8+
func NewBIP39Mnemonic(entropySize int) (string, error) {
9+
entropy, err := bip39.NewEntropy(entropySize)
10+
if err != nil {
11+
return "", err
12+
}
13+
14+
mnemonic, err := bip39.NewMnemonic(entropy)
15+
if err != nil {
16+
return "", err
17+
}
18+
19+
return mnemonic, nil
20+
}

offchain/ocr/ocr_test.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package ocr
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"testing"
7+
8+
"github.com/cosmos/go-bip39"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestNewBIP39Mnemonic(t *testing.T) {
14+
t.Parallel()
15+
16+
tests := []struct {
17+
name string
18+
entropySize int
19+
expectError bool
20+
}{
21+
{
22+
name: "valid entropy 128 bits",
23+
entropySize: 128,
24+
expectError: false,
25+
},
26+
{
27+
name: "valid entropy 160 bits",
28+
entropySize: 160,
29+
expectError: false,
30+
},
31+
{
32+
name: "valid entropy 192 bits",
33+
entropySize: 192,
34+
expectError: false,
35+
},
36+
{
37+
name: "valid entropy 224 bits",
38+
entropySize: 224,
39+
expectError: false,
40+
},
41+
{
42+
name: "valid entropy 256 bits",
43+
entropySize: 256,
44+
expectError: false,
45+
},
46+
{
47+
name: "invalid entropy size - too small",
48+
entropySize: 127,
49+
expectError: true,
50+
},
51+
{
52+
name: "invalid entropy size - too large",
53+
entropySize: 257,
54+
expectError: true,
55+
},
56+
{
57+
name: "invalid entropy size - not multiple of 32",
58+
entropySize: 129,
59+
expectError: true,
60+
},
61+
{
62+
name: "zero entropy size",
63+
entropySize: 0,
64+
expectError: true,
65+
},
66+
{
67+
name: "negative entropy size",
68+
entropySize: -128,
69+
expectError: true,
70+
},
71+
}
72+
73+
for _, tt := range tests {
74+
t.Run(tt.name, func(t *testing.T) {
75+
t.Parallel()
76+
77+
mnemonic, err := NewBIP39Mnemonic(tt.entropySize)
78+
79+
if tt.expectError {
80+
require.Error(t, err)
81+
assert.Empty(t, mnemonic)
82+
} else {
83+
require.NoError(t, err)
84+
assert.NotEmpty(t, mnemonic)
85+
86+
// Validate that the returned mnemonic is a valid BIP39 mnemonic
87+
assert.True(t, bip39.IsMnemonicValid(mnemonic), "generated mnemonic should be valid")
88+
89+
// Check word count based on entropy size
90+
words := strings.Fields(mnemonic)
91+
expectedWordCount := (tt.entropySize + tt.entropySize/32) / 11
92+
assert.Len(t, words, expectedWordCount, "word count should match expected for entropy size")
93+
94+
// Verify mnemonic can be converted back to bytes
95+
_, err := bip39.MnemonicToByteArray(mnemonic)
96+
require.NoError(t, err, "should be able to convert mnemonic to byte array")
97+
}
98+
})
99+
}
100+
}
101+
102+
func TestNewBIP39Mnemonic_Uniqueness(t *testing.T) {
103+
t.Parallel()
104+
105+
const entropySize = 256
106+
const numMnemonics = 100
107+
108+
mnemonics := make(map[string]bool)
109+
110+
for range numMnemonics {
111+
mnemonic, err := NewBIP39Mnemonic(entropySize)
112+
require.NoError(t, err)
113+
require.NotEmpty(t, mnemonic)
114+
115+
// Ensure each generated mnemonic is unique
116+
assert.False(t, mnemonics[mnemonic], "generated mnemonic should be unique")
117+
mnemonics[mnemonic] = true
118+
119+
// Validate the mnemonic
120+
assert.True(t, bip39.IsMnemonicValid(mnemonic))
121+
}
122+
123+
assert.Len(t, mnemonics, numMnemonics, "should have generated exactly %d unique mnemonics", numMnemonics)
124+
}
125+
126+
func TestNewBIP39Mnemonic_WordCount(t *testing.T) {
127+
t.Parallel()
128+
129+
tests := []struct {
130+
entropySize int
131+
expectedWordCount int
132+
}{
133+
{128, 12}, // 128 bits -> 12 words
134+
{160, 15}, // 160 bits -> 15 words
135+
{192, 18}, // 192 bits -> 18 words
136+
{224, 21}, // 224 bits -> 21 words
137+
{256, 24}, // 256 bits -> 24 words
138+
}
139+
140+
for _, tt := range tests {
141+
t.Run(fmt.Sprintf("entropy_%d_bits", tt.entropySize), func(t *testing.T) {
142+
t.Parallel()
143+
144+
mnemonic, err := NewBIP39Mnemonic(tt.entropySize)
145+
require.NoError(t, err)
146+
147+
words := strings.Fields(mnemonic)
148+
assert.Len(t, words, tt.expectedWordCount,
149+
"entropy size %d should generate %d words", tt.entropySize, tt.expectedWordCount)
150+
})
151+
}
152+
}

0 commit comments

Comments
 (0)