Initial Commit

This commit is contained in:
Jimmy Song 2017-04-24 17:19:04 -07:00
commit eb6b16f450
12 changed files with 450 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*~

37
address.go Normal file
View file

@ -0,0 +1,37 @@
package moneroutil
import (
"bytes"
)
type Address struct {
network int
spendingKey []byte
viewingKey []byte
}
func (a *Address) Base58() (result string) {
prefix := []byte{byte(a.network)}
checksum := Checksum(prefix, a.spendingKey, a.viewingKey)
result = EncodeMoneroBase58(prefix, a.spendingKey, a.viewingKey, checksum)
return
}
func NewAddress(address string) (result *Address, err string) {
raw := DecodeMoneroBase58(address)
if len(raw) != 69 {
err = "Address is the wrong length"
return
}
checksum := Checksum(raw[:65])
if bytes.Compare(checksum, raw[65:]) != 0 {
err = "Checksum does not validate"
return
}
result = &Address{
network: int(raw[0]),
spendingKey: raw[1:33],
viewingKey: raw[33:65],
}
return
}

90
address_test.go Normal file
View file

@ -0,0 +1,90 @@
package moneroutil
import (
"bytes"
"encoding/hex"
"testing"
)
func TestAddressError(t *testing.T) {
_, err := NewAddress("")
want := "Address is the wrong length"
if err != want {
t.Errorf("want: %s, got: %s", want, err)
}
_, err = NewAddress("46w3n5EGhBeZkYmKvQRsd8UK9GhvcbYWQDobJape3NLMMFEjFZnJ3CnRmeKspubQGiP8iMTwFEX2QiBsjUkjKT4SSPd3fK1")
want = "Checksum does not validate"
if err != want {
t.Errorf("want: %s, got: %s", want, err)
}
}
func TestAddress(t *testing.T) {
tests := []struct {
name string
network int
spendingKeyHex string
viewingKeyHex string
address string
}{
{
name: "generic",
network: MainNetwork,
spendingKeyHex: "8c1a9d5ff5aaf1c3cdeb2a1be62f07a34ae6b15fe47a254c8bc240f348271679",
viewingKeyHex: "0a29b163e392eb9416a52907fd7d3b84530f8d02ff70b1f63e72fdcb54cf7fe1",
address: "46w3n5EGhBeZkYmKvQRsd8UK9GhvcbYWQDobJape3NLMMFEjFZnJ3CnRmeKspubQGiP8iMTwFEX2QiBsjUkjKT4SSPd3fKp",
},
{
name: "generic 2",
network: MainNetwork,
spendingKeyHex: "5007b84275af9a173c2080683afce90b2157ab640c18ddd5ce3e060a18a9ce99",
viewingKeyHex: "27024b45150037b677418fcf11ba9675494ffdf994f329b9f7a8f8402b7934a0",
address: "44f1Y84r9Lu4tQdLWRxV122rygfhUeVBrcmBaqcYCwUHScmf1ht8DFLXX9YN4T7nPPLcpqYLUdrFiY77nQYeH9RuK9gg4p6",
},
{
name: "require 1 padding in middle",
network: MainNetwork,
spendingKeyHex: "6add197bd82866e8bfbf1dc2fdf49873ec5f679059652da549cd806f2b166756",
viewingKeyHex: "f5cf2897088fda0f7ac1c42491ed7d558a46ee41d0c81d038fd53ff4360afda0",
address: "45fzHekTd5FfvxWBPYX2TqLPbtWjaofxYUeWCi6BRQXYFYd85sY2qw73bAuKhqY7deFJr6pN3STY81bZ9x2Zf4nGKASksqe",
},
{
name: "require 1 padding in last chunk",
network: MainNetwork,
spendingKeyHex: "50defe92d88b19aaf6bf66f061dd4380b79866a4122b25a03bceb571767dbe7b",
viewingKeyHex: "f8f6f28283921bf5a17f0bcf4306233fc25ce9b6276154ad0de22aebc5c67702",
address: "44grjkXtDHJVbZgtU1UKnrNXidcHfZ3HWToU5WjR3KgHMjgwrYLjXC6i5vm3HCp4vnBfYaNEyNiuZVwqtHD2SenS1JBRyco",
},
{
name: "testnet",
network: TestNetwork,
spendingKeyHex: "8de9cce254e60cd940abf6c77ef344c3a21fad74320e45734fbfcd5870e5c875",
viewingKeyHex: "27024b45150037b677418fcf11ba9675494ffdf994f329b9f7a8f8402b7934a0",
address: "9xYZvCDf6aFdLd7Qawg5XHZitWLKoeFvcLHfe5GxsGCFLbXSWeQNKciXX9YN4T7nPPLcpqYLUdrFiY77nQYeH9RuK9bogZJ",
},
}
var base58 string
var spendingKey, viewingKey []byte
for _, test := range tests {
spendingKey, _ = hex.DecodeString(test.spendingKeyHex)
viewingKey, _ = hex.DecodeString(test.viewingKeyHex)
address, _ := NewAddress(test.address)
if address.network != test.network {
t.Errorf("%s: want: %d, got: %d", test.name, test.network, address.network)
continue
}
if bytes.Compare(address.spendingKey, spendingKey) != 0 {
t.Errorf("%s: want: %x, got: %x", test.name, spendingKey, address.spendingKey)
continue
}
if bytes.Compare(address.viewingKey, viewingKey) != 0 {
t.Errorf("%s: want: %x, got: %x", test.name, viewingKey, address.viewingKey)
continue
}
base58 = address.Base58()
if base58 != test.address {
t.Errorf("%s: want: %s, got: %s", test.name, test.address, base58)
continue
}
}
}

77
base58.go Normal file
View file

@ -0,0 +1,77 @@
package moneroutil
import (
"math/big"
"strings"
)
const BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
var base58Lookup = map[string]int{
"1": 0, "2": 1, "3": 2, "4": 3, "5": 4, "6": 5, "7": 6, "8": 7,
"9": 8, "A": 9, "B": 10, "C": 11, "D": 12, "E": 13, "F": 14, "G": 15,
"H": 16, "J": 17, "K": 18, "L": 19, "M": 20, "N": 21, "P": 22, "Q": 23,
"R": 24, "S": 25, "T": 26, "U": 27, "V": 28, "W": 29, "X": 30, "Y": 31,
"Z": 32, "a": 33, "b": 34, "c": 35, "d": 36, "e": 37, "f": 38, "g": 39,
"h": 40, "i": 41, "j": 42, "k": 43, "m": 44, "n": 45, "o": 46, "p": 47,
"q": 48, "r": 49, "s": 50, "t": 51, "u": 52, "v": 53, "w": 54, "x": 55,
"y": 56, "z": 57,
}
var bigBase = big.NewInt(58)
func encodeChunk(raw []byte, padding int) (result string) {
remainder := new(big.Int)
remainder.SetBytes(raw)
bigZero := new(big.Int)
for remainder.Cmp(bigZero) > 0 {
current := new(big.Int)
remainder.DivMod(remainder, bigBase, current)
result = string(BASE58[current.Int64()]) + result
}
if len(result) < padding {
result = strings.Repeat("1", (padding-len(result))) + result
}
return
}
func decodeChunk(encoded string) (result []byte) {
bigResult := big.NewInt(0)
currentMultiplier := big.NewInt(1)
tmp := new(big.Int)
for i := len(encoded) - 1; i >= 0; i-- {
tmp.SetInt64(int64(base58Lookup[string(encoded[i])]))
tmp.Mul(currentMultiplier, tmp)
bigResult.Add(bigResult, tmp)
currentMultiplier.Mul(currentMultiplier, bigBase)
}
result = bigResult.Bytes()
return
}
func EncodeMoneroBase58(data ...[]byte) (result string) {
var combined []byte
for _, item := range data {
combined = append(combined, item...)
}
length := len(combined)
rounds := length / 8
for i := 0; i < rounds; i++ {
result += encodeChunk(combined[i*8:(i+1)*8], 11)
}
if length%8 > 0 {
result += encodeChunk(combined[rounds*8:], 7)
}
return
}
func DecodeMoneroBase58(data string) (result []byte) {
length := len(data)
rounds := length / 11
for i := 0; i < rounds; i++ {
result = append(result, decodeChunk(data[i*11:(i+1)*11])...)
}
if length%11 > 0 {
result = append(result, decodeChunk(data[rounds*11:])...)
}
return
}

15
block.go Normal file
View file

@ -0,0 +1,15 @@
package moneroutil
type BlockHeader struct {
majorVersion uint8
minorVersion uint8
timeStamp uint64
previousHash Hash
nonce uint32
}
type Block struct {
BlockHeader
MinerTx Transaction
TxHashes []Hash
}

6
const.go Normal file
View file

@ -0,0 +1,6 @@
package moneroutil
const (
MainNetwork = 18
TestNetwork = 53
)

10
example_transaction Normal file

File diff suppressed because one or more lines are too long

20
keccak.go Normal file
View file

@ -0,0 +1,20 @@
package moneroutil
import (
"github.com/ebfe/keccak"
)
func Keccak256(data ...[]byte) (result []byte) {
h := keccak.New256()
for _, b := range data {
h.Write(b)
}
result = h.Sum(nil)
return
}
func Checksum(data ...[]byte) (result []byte) {
keccak256 := Keccak256(data...)
result = keccak256[:4]
return
}

53
ringsignature.go Normal file
View file

@ -0,0 +1,53 @@
package moneroutil
type Key [32]byte
type ctKey struct {
destination Key
mask Key
}
type ecdhTuple struct {
mask Key
amount Key
senderPk Key
}
type RingSignatureBase struct {
ringSigType uint8
message Key
mixRing [][]ctKey
pseudoOuts []Key
ecdhInfo []ecdhTuple
outPk []ctKey
fee uint64
}
type Key64 [64]Key
type boroSig struct {
s0 Key64
s1 Key64
ee Key
}
type mgSig struct {
ss [][]Key
cc Key
ii []Key
}
type rangeSig struct {
asig boroSig
ci Key64
}
type RctSigPrunable struct {
rangeSigs []rangeSig
MGs []mgSig
}
type RingSignature struct {
RingSignatureBase
RctSigPrunable
}

73
transaction.go Normal file
View file

@ -0,0 +1,73 @@
package moneroutil
type Hash [32]byte
type PubKey [32]byte
type Signature struct {
c []byte
r []byte
}
type txOutToScript struct {
pubKeys []PubKey
script []byte
}
type txOutToScriptHash struct {
hash Hash
}
type txOutToKey struct {
pubkey PubKey
}
type TxOutTargeter interface {
TxOutTarget() []byte
}
type txInGen struct {
height uint64
}
type txInToScript struct {
prev []byte
prevout uint64
sigSet []byte
}
type txInToScriptHash struct {
prev Hash
prevout uint64
txOutToScript []byte
sigSet []byte
}
type txInToKey struct {
amount uint64
keyOffsets []uint64
keyImage PubKey
}
type TxInMaker interface {
TxIn() []byte
}
type TxOut struct {
amount uint64
targets TxOutTargeter
}
type TransactionPrefix struct {
version uint32
unlockTime uint64
vin []TxInMaker
vout TxOut
extra []byte
}
type Transaction struct {
TransactionPrefix
signatures []Signature
ringSignatures []RingSignature
hash []byte
blobSize uint32
}

19
varint.go Normal file
View file

@ -0,0 +1,19 @@
package moneroutil
func ReadVarInt(varInt []byte) (result int64) {
for i, b := range varInt {
result += (int64(b) & 0x7f) << uint(i*7)
if int64(b)&0x80 == 0 {
break
}
}
return
}
func WriteVarInt(num int64) (result []byte) {
for ; num >= 0x80; num >>= 7 {
result = append(result, byte((num&0x7f)|0x80))
}
result = append(result, byte(num))
return
}

49
varint_test.go Normal file
View file

@ -0,0 +1,49 @@
package moneroutil
import (
"bytes"
"testing"
)
func TestVarInt(t *testing.T) {
tests := []struct {
name string
varInt []byte
want int64
}{
{
name: "1 byte",
varInt: []byte{0x01},
want: 1,
},
{
name: "3 bytes",
varInt: []byte{0x8f, 0xd6, 0x17},
want: 387855,
},
{
name: "4 bytes",
varInt: []byte{0x80, 0x92, 0xf4, 0x01},
want: 4000000,
},
{
name: "7 bytes",
varInt: []byte{0x80, 0xc0, 0xca, 0xf3, 0x84, 0xa3, 0x02},
want: 10000000000000,
},
}
var got int64
var gotVarInt []byte
for _, test := range tests {
gotVarInt = WriteVarInt(test.want)
if bytes.Compare(gotVarInt, test.varInt) != 0 {
t.Errorf("%s: varint want %x, got %x", test.name, test.varInt, gotVarInt)
continue
}
got = ReadVarInt(test.varInt)
if test.want != got {
t.Errorf("%s: want %d, got %d", test.name, test.want, got)
continue
}
}
}