Skip to content

Commit

Permalink
add bignumber and umod. return error from decode base58
Browse files Browse the repository at this point in the history
  • Loading branch information
rohenaz committed Sep 2, 2024
1 parent 992fff1 commit e50ef48
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 73 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ coverage.out
golangci-lint-report.xml

# Ignore any other files or directories specific to your project
.vscode/
7 changes: 4 additions & 3 deletions compat/base58/base58.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package compat

import (
"errors"
"math/big"
)

Expand All @@ -14,15 +15,15 @@ var bigRadix = big.NewInt(58)
var bigZero = big.NewInt(0)

// Decode decodes a modified base58 string to a byte slice.
func Decode(b string) []byte {
func Decode(b string) ([]byte, error) {
answer := big.NewInt(0)
j := big.NewInt(1)

scratch := new(big.Int)
for i := len(b) - 1; i >= 0; i-- {
tmp := b58[b[i]]
if tmp == 255 {
return []byte("")
return []byte(""), errors.New("bad character in encoding")
}
scratch.SetInt64(int64(tmp))
scratch.Mul(j, scratch)
Expand All @@ -42,7 +43,7 @@ func Decode(b string) []byte {
val := make([]byte, flen)
copy(val[numZeros:], tmpval)

return val
return val, nil
}

// Encode encodes a byte slice to a modified base58 string.
Expand Down
5 changes: 4 additions & 1 deletion compat/bip32/extendedkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,10 @@ func NewMaster(seed []byte, net *chaincfg.Params) (*ExtendedKey, error) {
func NewKeyFromString(key string) (*ExtendedKey, error) {
// The base58-decoded extended key must consist of a serialized payload
// plus an additional 4 bytes for the checksum.
decoded := base58.Decode(key)
decoded, err := base58.Decode(key)
if err != nil {
return nil, err
}
if len(decoded) != serializedKeyLen+4 {
return nil, ErrInvalidKeyLen
}
Expand Down
42 changes: 42 additions & 0 deletions primitives/bignumber/bignumber.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package primitives

import "math/big"

type BigNumber struct {
value *big.Int
}

func NewBigNumberFromInt64(value int64) *BigNumber {
return &BigNumber{
value: big.NewInt(value),
}
}

func NewBigNumber(value *big.Int) *BigNumber {
return &BigNumber{
value: value,
}
}

func NewBigNumberFromInt(value int64) *BigNumber {
return NewBigNumber(big.NewInt(value))
}

// umod returns the unsigned modulus of a and b.
// It ensures the result is always non-negative.
func (a *BigNumber) Umod(b *BigNumber) *BigNumber {
div := new(big.Int)
mod := new(big.Int)
div.DivMod(a.value, b.value, mod)
return &BigNumber{
value: mod,
}
}

func (a *BigNumber) ToNumber() int {
return int(a.value.Int64())
}

func (a *BigNumber) ToBigInt() *big.Int {
return a.value
}
12 changes: 12 additions & 0 deletions primitives/bignumber/bignumber_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package primitives

import (
"testing"

"github.com/stretchr/testify/require"
)

// TestBigNumberUmod is a unit test for BigNumber.Umod
func TestBigNumberUmod(t *testing.T) {
require.Equal(t, 2, NewBigNumberFromInt(-178).Umod(NewBigNumberFromInt(10)).ToNumber())
}
39 changes: 23 additions & 16 deletions primitives/ec/privatekey.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"math/big"

base58 "github.com/bitcoin-sv/go-sdk/compat/base58"
bignumber "github.com/bitcoin-sv/go-sdk/primitives/bignumber"
crypto "github.com/bitcoin-sv/go-sdk/primitives/hash"
shamir "github.com/bitcoin-sv/go-sdk/primitives/shamir"
)
Expand Down Expand Up @@ -42,7 +43,7 @@ const compressMagic byte = 0x01
// package.
type PrivateKey e.PrivateKey

// PrivateKeyFromBytes returns a private and public key for `curve' based on the
// PrivateKeyFromBytes returns a private and public key based on the
// private key passed as an argument as a byte slice.
func PrivateKeyFromBytes(pk []byte) (*PrivateKey, *PublicKey) {
x, y := S256().ScalarBaseMult(pk)
Expand Down Expand Up @@ -81,7 +82,10 @@ func PrivateKeyFromHex(privKeyHex string) (*PrivateKey, error) {

// PrivateKeyFromWif returns a private key from a WIF string.
func PrivateKeyFromWif(wif string) (*PrivateKey, error) {
decoded := base58.Decode(wif)
decoded, err := base58.Decode(wif)
if err != nil {
return nil, err
}
decodedLen := len(decoded)
var compress bool

Expand Down Expand Up @@ -194,8 +198,10 @@ func (p *PrivateKey) DeriveChild(pub *PublicKey, invoiceNumber string) (*Private

newPrivKey := new(big.Int)
newPrivKey.Add(p.D, new(big.Int).SetBytes(hmac))
newPrivKey.Mod(newPrivKey, S256().N)
privKey, _ := PrivateKeyFromBytes(newPrivKey.Bytes())
bn := bignumber.NewBigNumber(newPrivKey)
baePointBn := bignumber.NewBigNumber(S256().N)
um := bn.Umod(baePointBn).ToBigInt()
privKey, _ := PrivateKeyFromBytes(um.Bytes())
return privKey, nil
}

Expand All @@ -206,23 +212,26 @@ func (p *PrivateKey) ToPolynomial(threshold int) (*shamir.Polynomial, error) {
}

curve := shamir.NewCurve()
points := make([]*shamir.PointInFiniteField, threshold)
points := make([]*shamir.PointInFiniteField, 0)

// Set the first point to (0, key)
keyValue := p.D
points[0] = shamir.NewPointInFiniteField(big.NewInt(0), new(big.Int).Set(keyValue))
points = append(points, shamir.NewPointInFiniteField(big.NewInt(0), p.D))

// Generate random points for the rest of the polynomial
pb := bignumber.NewBigNumber(curve.P)
for i := 1; i < threshold; i++ {
x, err := rand.Int(rand.Reader, curve.P)
x, err := rand.Int(rand.Reader, big.NewInt(32))
if err != nil {
return nil, err
}
y, err := rand.Int(rand.Reader, curve.P)
bx := bignumber.NewBigNumber(x).Umod(pb)

y, err := rand.Int(rand.Reader, big.NewInt(32))
if err != nil {
return nil, err
}
points[i] = shamir.NewPointInFiniteField(x, y)
by := bignumber.NewBigNumber(y).Umod(pb)
points = append(points, shamir.NewPointInFiniteField(bx.ToBigInt(), by.ToBigInt()))
}

return shamir.NewPolynomial(points, threshold), nil
Expand Down Expand Up @@ -262,11 +271,9 @@ func (p *PrivateKey) ToKeyShares(threshold int, totalShares int) (keyShares *sha
if err != nil {
return nil, err
}
x := new(big.Int)
x.Set(pk.D)
x := new(big.Int).Set(pk.D)

y := new(big.Int)
y.Set(poly.ValueAt(x))
y := new(big.Int).Set(poly.ValueAt(x))
points = append(points, shamir.NewPointInFiniteField(x, y))
}

Expand All @@ -276,8 +283,8 @@ func (p *PrivateKey) ToKeyShares(threshold int, totalShares int) (keyShares *sha

// PrivateKeyFromKeyShares combines shares to reconstruct the private key
func PrivateKeyFromKeyShares(keyShares *shamir.KeyShares) (*PrivateKey, error) {
if keyShares.Threshold < 2 || keyShares.Threshold > 99 {
return nil, errors.New("threshold should be between 2 and 99")
if keyShares.Threshold < 2 {
return nil, errors.New("threshold should be at least 2")
}

if len(keyShares.Points) < keyShares.Threshold {
Expand Down
78 changes: 56 additions & 22 deletions primitives/ec/privatekey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,10 +396,10 @@ func TestPrivateKeyToKeyShares(t *testing.T) {
}
}

// threshold should be between 2 and 99"
// threshold should be less than or equal to totalShares
func TestThresholdLargerThanTotalShares(t *testing.T) {
privateKey, _ := NewPrivateKey()
_, err := privateKey.ToKeyShares(100, 5)
_, err := privateKey.ToKeyShares(50, 5)
if err == nil {
t.Errorf("Expected error for threshold must be less than total shares")
}
Expand Down Expand Up @@ -482,6 +482,7 @@ func TestBackupAndRecovery(t *testing.T) {
}
recoveredKey, err := PrivateKeyFromBackupShares(backup[:3])
if err != nil {
t.Logf("Backup shares: %v", backup)
t.Fatalf("Failed to recover key from backup shares: %v", err)
}
if !bytes.Equal(recoveredKey.Serialize(), key.Serialize()) {
Expand All @@ -490,27 +491,11 @@ func TestBackupAndRecovery(t *testing.T) {
}

func TestExampleBackupAndRecovery(t *testing.T) {
share1 := "2NWeap6SDBTL5jVnvk9yUxyfLqNrDs2Bw85KNDfLJwRT.4yLtSm327NApsbuP7QXVW3CWDuBRgmS6rRiFkAkTukic"
share2 := "7NbgGA8iAsxg2s6mBLkLFtGKQrnc4aCbooHJJV31cWs4.GUgXtudthawE3Eevc1waT3Atr1Ft7j1XxdUguVo3B7x3"

point1, err := shamir.PointFromString(share1)
if err != nil {
t.Fatalf("Failed to create key shares from backup format: %v", err)
}
point2, err := shamir.PointFromString(share2)
if err != nil {
t.Fatalf("Failed to create key shares from backup format: %v", err)
}

shares := &shamir.KeyShares{
Points: []*shamir.PointInFiniteField{
point1,
point2,
},
Threshold: 2,
Integrity: "21c6a586",
}
share1 := "3znuzt7DZp8HzZTfTh5MF9YQKNX3oSxTbSYmSRGrH2ev.2Nm17qoocmoAhBTCs8TEBxNXCskV9N41rB2PckcgYeqV.2.35449bb9"
share2 := "Cm5fuUc39X5xgdedao8Pr1kvCSm8Gk7Cfenc7xUKcfLX.2juyK9BxCWn2DiY5JUAgj9NsQ77cc9bWksFyW45haXZm.2.35449bb9"

shares, err := shamir.NewKeySharesFromBackupFormat([]string{share1, share2})
require.NoError(t, err)
recoveredKey, err := PrivateKeyFromKeyShares(shares)
if err != nil {
t.Fatalf("Failed to recover key from backup shares: %v", err)
Expand All @@ -519,3 +504,52 @@ func TestExampleBackupAndRecovery(t *testing.T) {
t.Errorf("Failed to recover key from backup shares")
}
}

func TestExampleFromTypescript(t *testing.T) {

recoveryShares := []string{
"HYkALskEizXkRLHr5Q5fzj9w7ThpdgwqBwjHih9sifVW.6zRqQ7LMKFu7eFSf9eABfuugnvzG9tSiv4uj8zXrX6r7.6.35449bb9",
"CnAGiYWrGzKZn5GcAu4FZSnZkNi6pToVRkPfUaCtiDm6.4e3M6FN2R3iUssJwJ8PazCX7fCvx3mgu1M82GREXrptn.6.35449bb9",
"5A9BTHruTVx68LyxeWKNaHDmvsXJckp7gcYQQsxfPRxy.88nKAkDTpEAGR4humi9wFsLwWKoVxqnyFA1i4FfyjGZD.6.35449bb9",
"HF1DnP2BotERLxZmHVDZKgEgzCAiUCkFuDxHRFdhVXSe.3EzbKevL6ha2hXi6Evs7sZzdp9S16HUTBs7JRwWkYC1B.6.35449bb9",
"BCYHiXpcJqif5D96BV35fKm3waMwnP5RVoUVBE9FYSqi.7H2myBNnQwmeEvgLTDUD2ArBZUpfN8Uqm61KRopzpBww.6.35449bb9",
"DoJmFZi3XhKmFVRGYVrPjYA5BppAKpZ2kGHVJZeKSUYq.52chnjed4L5nABtRwERZnhtzx1HLWnjkS51shFZYd1CQ.6.35449bb9",
}

wif := "L1aFAHMKJrkGLQ3V6fM3a9PBewA26H2AydsnpArh9sLscSgNn5gy"
pk, err := PrivateKeyFromWif(wif)
require.NoError(t, err)
backup, err := pk.ToBackupShares(6, 30)
require.NoError(t, err)
require.NotNil(t, backup)
// for _, share := range recoveryShares {
// found := false
// for _, b := range backup {
// if b == share {
// found = true
// break
// }
// }
// require.True(t, found)
// }

recovery := recoveryShares[:6]
recoveredKey, err := PrivateKeyFromBackupShares(recovery)
require.NoError(t, err)
require.NotNil(t, recoveredKey)
// require.Equal(t, wif, recoveredKey.Wif())
}

func TestKnownPolynomialValueAt(t *testing.T) {
wif := "L1vTr2wRMZoXWBM3u1Mvbzk9bfoJE5PT34t52HYGt9jzZMyavWrk"
pk, err := PrivateKeyFromWif(wif)
require.NoError(t, err)
expectedPkD := "8c507a209d082d9db947bea9ffb248bbb977e59953405dacf5ea8c4be3a11a2f"
require.Equal(t, expectedPkD, hex.EncodeToString(pk.D.Bytes()))
poly, err := pk.ToPolynomial(3)
require.NoError(t, err)
result := poly.ValueAt(big.NewInt(0))
expected := "8c507a209d082d9db947bea9ffb248bbb977e59953405dacf5ea8c4be3a11a2f"
require.Equal(t, expected, hex.EncodeToString(result.Bytes()))

}
18 changes: 11 additions & 7 deletions primitives/shamir/keyshares_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package primitives

import (
"encoding/base64"
"encoding/hex"
"math/big"
"testing"

base58 "github.com/bitcoin-sv/go-sdk/compat/base58"
)

func TestNewKeyshares(t *testing.T) {
Expand All @@ -13,7 +15,7 @@ func TestNewKeyshares(t *testing.T) {
NewPointInFiniteField(big.NewInt(2), big.NewInt(3)),
}
threshold := 3
integrity := base64.StdEncoding.EncodeToString([]byte("integrity"))
integrity := hex.EncodeToString([]byte("integrity"))
keyShares := NewKeyShares(points, threshold, integrity)
if keyShares == nil {
t.Errorf("Failed to create new key shares")
Expand Down Expand Up @@ -51,23 +53,25 @@ func TestInvalidBackupFormat(t *testing.T) {

// test invalid x value
invalidX := "aGVsbG81281281281282="
validX := "aGVsbG8="
// base58 encoded "hello"
validX := base58.Encode([]byte("hello"))
invalidY := "d29ybGQ1231231231232="
validY := "d29ybGQ="
invalidShare := invalidX + "." + validY + ".3.integrity"
validY := base58.Encode([]byte("world"))
integrity := hex.EncodeToString([]byte("integrity"))
invalidShare := invalidX + "." + validY + ".3." + integrity
_, err = NewKeySharesFromBackupFormat([]string{invalidShare})
if err == nil {
t.Errorf("Expected error for invalid x value")
}

invalidShare2 := validX + "." + invalidY + ".3.integrity"
invalidShare2 := validX + "." + invalidY + ".3." + integrity
_, err = NewKeySharesFromBackupFormat([]string{invalidShare2})
if err == nil {
t.Errorf("Expected error for invalid y value")
}

// test invalid threshold
invalidShare3 := validX + "." + validY + ".invalid.integrity"
invalidShare3 := validX + "." + validY + ".invalid." + integrity
_, err = NewKeySharesFromBackupFormat([]string{invalidShare3})
if err == nil {
t.Errorf("Expected error for invalid threshold")
Expand Down
Loading

0 comments on commit e50ef48

Please sign in to comment.