Initial Commit
This commit is contained in:
commit
eb6b16f450
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*~
|
37
address.go
Normal file
37
address.go
Normal 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
90
address_test.go
Normal 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
77
base58.go
Normal 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
15
block.go
Normal 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
6
const.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package moneroutil
|
||||
|
||||
const (
|
||||
MainNetwork = 18
|
||||
TestNetwork = 53
|
||||
)
|
10
example_transaction
Normal file
10
example_transaction
Normal file
File diff suppressed because one or more lines are too long
20
keccak.go
Normal file
20
keccak.go
Normal 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
53
ringsignature.go
Normal 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
73
transaction.go
Normal 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
19
varint.go
Normal 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
49
varint_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
Reference in a new issue