github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/lib/btc/wallet.go (about) 1 package btc 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "errors" 7 "math/big" 8 9 "github.com/piotrnar/gocoin/lib/secp256k1" 10 ) 11 12 // PublicFromPrivate gets the ECDSA public key in Bitcoin protocol format, from the give private key. 13 func PublicFromPrivate(priv_key []byte, compressed bool) (res []byte) { 14 if compressed { 15 res = make([]byte, 33) 16 } else { 17 res = make([]byte, 65) 18 } 19 20 if !secp256k1.BaseMultiply(priv_key, res) { 21 res = nil 22 } 23 return 24 } 25 26 // VerifyKeyPair verifies the secret key's range and if a test message signed with it verifies OK. 27 // Returns nil if everything looks OK. 28 func VerifyKeyPair(priv []byte, publ []byte) error { 29 var sig Signature 30 31 const TestMessage = "Just some test message..." 32 hash := Sha2Sum([]byte(TestMessage)) 33 34 D := new(big.Int).SetBytes(priv) 35 36 if D.Cmp(big.NewInt(0)) == 0 { 37 return errors.New("pubkey value is zero") 38 } 39 40 if D.Cmp(&secp256k1.TheCurve.Order.Int) != -1 { 41 return errors.New("pubkey value is too big") 42 } 43 44 r, s, e := EcdsaSign(priv, hash[:]) 45 if e != nil { 46 return errors.New("EcdsaSign failed: " + e.Error()) 47 } 48 49 sig.R.Set(r) 50 sig.S.Set(s) 51 ok := EcdsaVerify(publ, sig.Bytes(), hash[:]) 52 if !ok { 53 return errors.New("EcdsaVerify failed") 54 } 55 56 // Now the same using a schnorr sign/verify 57 randata := Sha2Sum(append(hash[:], []byte(TestMessage)...)) 58 ssig := secp256k1.SchnorrSign(hash[:], priv, randata[:]) 59 if len(ssig) != 64 { 60 return errors.New("SchnorrSign failed: " + hex.EncodeToString(ssig)) 61 } 62 63 ok = secp256k1.SchnorrVerify(publ[1:33], ssig, hash[:]) 64 if !ok { 65 return errors.New("SchnorrVerify failed") 66 } 67 68 return nil 69 } 70 71 // DeriveNextPrivate is used for implementing Type-2 determinitic keys. 72 // B_private_key = ( A_private_key + secret ) % N 73 func DeriveNextPrivate(p, s []byte) (toreturn []byte) { 74 var prv, secret big.Int 75 prv.SetBytes(p) 76 secret.SetBytes(s) 77 res := new(big.Int).Mod(new(big.Int).Add(&prv, &secret), &secp256k1.TheCurve.Order.Int).Bytes() 78 toreturn = make([]byte, 32) 79 copy(toreturn[32-len(res):], res) 80 return 81 } 82 83 // DeriveNextPublic is used for implementing Type-2 determinitic keys. 84 // B_public_key = G * secret + A_public_key 85 func DeriveNextPublic(public, secret []byte) (out []byte) { 86 out = make([]byte, len(public)) 87 secp256k1.BaseMultiplyAdd(public, secret, out) 88 return 89 } 90 91 // NewSpendOutputs returns one TxOut record. 92 func NewSpendOutputs(addr *BtcAddr, amount uint64, testnet bool) ([]*TxOut, error) { 93 out := new(TxOut) 94 out.Value = amount 95 out.Pk_script = addr.OutScript() 96 return []*TxOut{out}, nil 97 } 98 99 // PrivateAddr is a Base58 encoded private address with checksum and its corresponding public key/address. 100 type PrivateAddr struct { 101 Version byte 102 Key []byte 103 *BtcAddr 104 } 105 106 func NewPrivateAddr(key []byte, ver byte, compr bool) (ad *PrivateAddr) { 107 ad = new(PrivateAddr) 108 ad.Version = ver 109 ad.Key = key 110 pub := PublicFromPrivate(key, compr) 111 if pub == nil { 112 panic("PublicFromPrivate error") 113 } 114 ad.BtcAddr = NewAddrFromPubkey(pub, ver-0x80) 115 return 116 } 117 118 func DecodePrivateAddr(s string) (*PrivateAddr, error) { 119 pkb := Decodeb58(s) 120 121 if pkb == nil { 122 return nil, errors.New("Decodeb58 failed") 123 } 124 125 if len(pkb) < 37 { 126 return nil, errors.New("Decoded data too short") 127 } 128 129 if len(pkb) > 38 { 130 return nil, errors.New("Decoded data too long") 131 } 132 133 var sh [32]byte 134 ShaHash(pkb[:len(pkb)-4], sh[:]) 135 if !bytes.Equal(sh[:4], pkb[len(pkb)-4:]) { 136 return nil, errors.New("Checksum error") 137 } 138 139 return NewPrivateAddr(pkb[1:33], pkb[0], len(pkb) == 38 && pkb[33] == 1), nil 140 } 141 142 // String returns the Base58 encoded private key (with checksum). 143 func (ad *PrivateAddr) String() string { 144 var ha [32]byte 145 buf := new(bytes.Buffer) 146 buf.WriteByte(ad.Version) 147 buf.Write(ad.Key) 148 if ad.BtcAddr.IsCompressed() { 149 buf.WriteByte(1) 150 } 151 ShaHash(buf.Bytes(), ha[:]) 152 buf.Write(ha[:4]) 153 return Encodeb58(buf.Bytes()) 154 }