Implement shielded addresses
This commit is contained in:
parent
768d1bac95
commit
1f087f72d8
|
@ -24,6 +24,8 @@
|
|||
"standard": "10.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"bs58check": "2.0.2",
|
||||
"libsodium-wrappers-sumo": "0.5.1",
|
||||
"typeforce": "1.11.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
'use strict'
|
||||
|
||||
var bs58check = require('bs58check')
|
||||
var prf = require('./prf')
|
||||
var typeforce = require('typeforce')
|
||||
var types = require('./types')
|
||||
var util = require('./util')
|
||||
|
||||
function fromBase58Check (address) {
|
||||
var payload = bs58check.decode(address)
|
||||
var version = payload.readUInt16BE(0)
|
||||
var data = payload.slice(2)
|
||||
|
||||
return { data: data, version: version }
|
||||
}
|
||||
|
||||
function toBase58Check (data, version) {
|
||||
typeforce(types.tuple(types.Buffer, types.UInt16), arguments)
|
||||
|
||||
var payload = Buffer.alloc(data.length + 2)
|
||||
payload.writeUInt16BE(version, 0)
|
||||
data.copy(payload, 2)
|
||||
|
||||
return bs58check.encode(payload)
|
||||
}
|
||||
|
||||
function PaymentAddress (aPk, pkEnc) {
|
||||
typeforce(types.tuple(types.Buffer256bit, types.Buffer256bit), arguments)
|
||||
this.a_pk = aPk
|
||||
this.pk_enc = pkEnc
|
||||
}
|
||||
|
||||
PaymentAddress.fromBuffer = function (buffer, __noStrict) {
|
||||
var offset = 0
|
||||
function readSlice (n) {
|
||||
offset += n
|
||||
return buffer.slice(offset - n, offset)
|
||||
}
|
||||
|
||||
var aPk = readSlice(32)
|
||||
var pkEnc = readSlice(32)
|
||||
var addr = new PaymentAddress(aPk, pkEnc)
|
||||
|
||||
if (__noStrict) return addr
|
||||
if (offset !== buffer.length) throw new Error('PaymentAddress has unexpected data')
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
PaymentAddress.prototype.byteLength = function () {
|
||||
return 64
|
||||
}
|
||||
|
||||
PaymentAddress.prototype.toBuffer = function () {
|
||||
var buffer = Buffer.alloc(this.byteLength())
|
||||
|
||||
var offset = 0
|
||||
function writeSlice (slice) {
|
||||
slice.copy(buffer, offset)
|
||||
offset += slice.length
|
||||
}
|
||||
|
||||
writeSlice(this.a_pk)
|
||||
writeSlice(this.pk_enc)
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
PaymentAddress.prototype.toZAddress = function (version) {
|
||||
return toBase58Check(this.toBuffer(), version)
|
||||
}
|
||||
|
||||
function SpendingKey (aSk) {
|
||||
typeforce(types.Buffer252bit, aSk)
|
||||
this.a_sk = aSk
|
||||
}
|
||||
|
||||
SpendingKey.random = function () {
|
||||
return new SpendingKey(util.random_uint252())
|
||||
}
|
||||
|
||||
SpendingKey.fromBuffer = function (buffer, __noStrict) {
|
||||
var offset = 0
|
||||
function readSlice (n) {
|
||||
offset += n
|
||||
return buffer.slice(offset - n, offset)
|
||||
}
|
||||
|
||||
var aSk = readSlice(32)
|
||||
var sk = new SpendingKey(aSk)
|
||||
|
||||
if (__noStrict) return sk
|
||||
if (offset !== buffer.length) throw new Error('SpendingKey has unexpected data')
|
||||
|
||||
return sk
|
||||
}
|
||||
|
||||
SpendingKey.prototype.byteLength = function () {
|
||||
return 32
|
||||
}
|
||||
|
||||
SpendingKey.prototype.toBuffer = function () {
|
||||
var buffer = Buffer.alloc(this.byteLength())
|
||||
|
||||
var offset = 0
|
||||
function writeSlice (slice) {
|
||||
slice.copy(buffer, offset)
|
||||
offset += slice.length
|
||||
}
|
||||
|
||||
writeSlice(this.a_sk)
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
SpendingKey.prototype.toZKey = function (version) {
|
||||
return toBase58Check(this.toBuffer(), version)
|
||||
}
|
||||
|
||||
SpendingKey.prototype.address = function () {
|
||||
return new PaymentAddress(
|
||||
prf.PRF_addr_a_pk(this.a_sk),
|
||||
util.generate_pubkey(util.generate_privkey(this.a_sk))
|
||||
)
|
||||
}
|
||||
|
||||
function fromZAddress (address, versionMap) {
|
||||
var decode = fromBase58Check(address)
|
||||
if (decode.version in versionMap) return versionMap[decode.version].fromBuffer(decode.data)
|
||||
throw new Error(address + ' has no matching z-address')
|
||||
}
|
||||
|
||||
function fromZKey (key, versionMap) {
|
||||
var decode = fromBase58Check(key)
|
||||
if (decode.version in versionMap) return versionMap[decode.version].fromBuffer(decode.data)
|
||||
throw new Error(key + ' has no matching z-key')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
PaymentAddress: PaymentAddress,
|
||||
SpendingKey: SpendingKey,
|
||||
fromZAddress: fromZAddress,
|
||||
fromZKey: fromZKey
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
module.exports = {
|
||||
JSDescription: require('./jsdescription')
|
||||
JSDescription: require('./jsdescription'),
|
||||
|
||||
address: require('./address')
|
||||
}
|
||||
|
|
13
src/types.js
13
src/types.js
|
@ -10,11 +10,22 @@ function BoolNum (value) {
|
|||
value <= 1
|
||||
}
|
||||
|
||||
// exposed, external API
|
||||
var PaymentAddress = typeforce.compile({
|
||||
a_pk: typeforce.BufferN(32),
|
||||
pk_enc: typeforce.BufferN(32)
|
||||
})
|
||||
var SpendingKey = typeforce.compile({
|
||||
a_sk: Buffer252bit
|
||||
})
|
||||
|
||||
// extend typeforce types with ours
|
||||
var types = {
|
||||
BoolNum: BoolNum,
|
||||
Buffer252bit: Buffer252bit,
|
||||
Buffer256bit: typeforce.BufferN(32)
|
||||
Buffer256bit: typeforce.BufferN(32),
|
||||
PaymentAddress: PaymentAddress,
|
||||
SpendingKey: SpendingKey
|
||||
}
|
||||
|
||||
for (var typeName in typeforce) {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
'use strict'
|
||||
|
||||
var prf = require('./prf')
|
||||
var sodium = require('libsodium-wrappers-sumo')
|
||||
var typeforce = require('typeforce')
|
||||
var types = require('./types')
|
||||
|
||||
function randomUint256 () {
|
||||
return Buffer.from(sodium.randombytes_buf(32))
|
||||
}
|
||||
|
||||
function randomUint252 () {
|
||||
var rand = Buffer.from(randomUint256())
|
||||
rand[0] &= 0x0F
|
||||
return rand
|
||||
}
|
||||
|
||||
function generatePrivkey (aSk) {
|
||||
var sk = prf.PRF_addr_sk_enc(aSk)
|
||||
|
||||
// Curve25519 clamping
|
||||
sk[0] &= 248
|
||||
sk[31] &= 127
|
||||
sk[31] |= 64
|
||||
|
||||
return sk
|
||||
}
|
||||
|
||||
function generatePubkey (skEnc) {
|
||||
typeforce(types.Buffer256bit, skEnc)
|
||||
|
||||
return Buffer.from(sodium.crypto_scalarmult_base(skEnc))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generate_privkey: generatePrivkey,
|
||||
generate_pubkey: generatePubkey,
|
||||
random_uint252: randomUint252,
|
||||
random_uint256: randomUint256
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/* global describe, it */
|
||||
|
||||
var assert = require('assert')
|
||||
|
||||
var address = require('../src/address')
|
||||
|
||||
var fixtures = require('./fixtures/address')
|
||||
|
||||
describe('PaymentAddress', function () {
|
||||
function fromRaw (raw) {
|
||||
var aPk = Buffer.from(raw.a_pk, 'hex')
|
||||
var pkEnc = Buffer.from(raw.pk_enc, 'hex')
|
||||
return new address.PaymentAddress(aPk, pkEnc)
|
||||
}
|
||||
|
||||
describe('fromZAddress', function () {
|
||||
fixtures.valid.forEach(function (f) {
|
||||
var versionMap = {}
|
||||
versionMap[f.addressVersion] = address.PaymentAddress
|
||||
|
||||
it('imports ' + f.address, function () {
|
||||
var actual = address.fromZAddress(f.address, versionMap)
|
||||
assert.strictEqual(
|
||||
actual.toZAddress(f.addressVersion),
|
||||
f.address,
|
||||
actual.toZAddress(f.addressVersion))
|
||||
})
|
||||
})
|
||||
|
||||
fixtures.invalid.fromZAddress.forEach(function (f) {
|
||||
var versionMap = {}
|
||||
versionMap[f.addressVersion] = address.PaymentAddress
|
||||
|
||||
it('throws on ' + f.exception, function () {
|
||||
assert.throws(function () {
|
||||
address.fromZAddress(f.address, versionMap)
|
||||
}, f.exception)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('toZAddress', function () {
|
||||
fixtures.valid.forEach(function (f) {
|
||||
it('exports ' + f.address, function () {
|
||||
var actual = fromRaw(f.raw)
|
||||
assert.strictEqual(
|
||||
actual.toZAddress(f.addressVersion),
|
||||
f.address,
|
||||
actual.toZAddress(f.addressVersion))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('SpendingKey', function () {
|
||||
function fromRaw (raw) {
|
||||
var aSk = Buffer.from(raw.a_sk, 'hex')
|
||||
return new address.SpendingKey(aSk)
|
||||
}
|
||||
|
||||
describe('fromZKey', function () {
|
||||
fixtures.valid.forEach(function (f) {
|
||||
var versionMap = {}
|
||||
versionMap[f.keyVersion] = address.SpendingKey
|
||||
|
||||
it('imports ' + f.key, function () {
|
||||
var actual = address.fromZKey(f.key, versionMap)
|
||||
assert.strictEqual(
|
||||
actual.toZKey(f.keyVersion),
|
||||
f.key,
|
||||
actual.toZKey(f.keyVersion))
|
||||
})
|
||||
})
|
||||
|
||||
fixtures.invalid.fromZKey.forEach(function (f) {
|
||||
var versionMap = {}
|
||||
versionMap[f.keyVersion] = address.SpendingKey
|
||||
|
||||
it('throws on ' + f.exception, function () {
|
||||
assert.throws(function () {
|
||||
address.fromZKey(f.key, versionMap)
|
||||
}, f.exception)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('toZKey', function () {
|
||||
fixtures.valid.forEach(function (f) {
|
||||
it('exports ' + f.key, function () {
|
||||
var actual = fromRaw(f.raw)
|
||||
assert.strictEqual(
|
||||
actual.toZKey(f.keyVersion),
|
||||
f.key,
|
||||
actual.toZKey(f.keyVersion))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('address', function () {
|
||||
fixtures.valid.forEach(function (f) {
|
||||
var versionMap = {}
|
||||
versionMap[f.keyVersion] = address.SpendingKey
|
||||
|
||||
it('correctly derives for ' + f.key, function () {
|
||||
var key = address.fromZKey(f.key, versionMap)
|
||||
var actual = key.address()
|
||||
assert.strictEqual(
|
||||
actual.toZAddress(f.addressVersion),
|
||||
f.address,
|
||||
actual.toZAddress(f.addressVersion))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
"valid": [
|
||||
{
|
||||
"raw": {
|
||||
"a_sk": "0d05ff283bfb519619b4ad0a2a417fcf3e0782de56e7ebd508cfbf365401d18f",
|
||||
"a_pk": "c47235aa758c7fffaa12726e7c3c11b48e116db5373215dabadbaaa6a6d5cde4",
|
||||
"pk_enc": "58230f1e86b26bd34c383e7503e28a195c5f4016f4ed8020b8e642c0b36baf08"
|
||||
},
|
||||
"keyVersion": 43830,
|
||||
"addressVersion": 5786,
|
||||
"key": "SKxthnmAMPWVea6ganJZzPjvFauoLDvgyHHbZy54qMpuFFh2L3nP",
|
||||
"address": "zcZviziuUB45WMY4jnV5Vmx6ZKDLyXzb12QbnWyDofTqZEbeJZF49vVNi75UgiuzXFytgJ6bJoq22gUsANBS4BjUfFudBMu"
|
||||
},
|
||||
{
|
||||
"raw": {
|
||||
"a_sk": "0fcdb3870de951df0fee40a35380127c4dcb7c964dab356faa4b43b0da4602fa",
|
||||
"a_pk": "0658e1ea67b8adf24b706c54949b35d8cf385080a84f06a318eeb195b04a1a35",
|
||||
"pk_enc": "d00fdb5cbf7c57c0df14ee44e24653debf3e7e3ae446cf29ffd9e1d59a74303a"
|
||||
},
|
||||
"keyVersion": 43830,
|
||||
"addressVersion": 5786,
|
||||
"key": "SKxuvoZevPsBHMhQ96wEN8rU4BCR3vwNFyA1W1AbsMMe2Ts4E2BC",
|
||||
"address": "zc94F8QLFHXR4unq4njynUGcQ3cpyC9CNeoZhrPq5VsKrwKDGea79mZPTuCmzixF5wFi9zNaXpRS2fPYaz4bNTUCkPMfgG1"
|
||||
},
|
||||
{
|
||||
"raw": {
|
||||
"a_sk": "0473fcb81c85f84f15b6449bb678500c4e675f26e8b47d3380a08442bd28a541",
|
||||
"a_pk": "141687f6df6c101be90517243281fe70f02bba59ab0a24bc804c49567d077450",
|
||||
"pk_enc": "40e2e2663801419181b5bd00d7e72e1e03f6aaab701726aa1363ebd1a27a250e"
|
||||
},
|
||||
"keyVersion": 44040,
|
||||
"addressVersion": 5814,
|
||||
"key": "ST13FvCzYJWFmDATmpVgeG14HXgHPSaNMkiU4ynyuNADq11xUEq1",
|
||||
"address": "ztLdfP3bNQ8kHeuxyZyoDHN79KKrpVJq9yMAZ4pKYup76sAG1oUH98egwYXFRjFWmUXHuuX2ypLtZE6k7ycYShJhMpw3g8K"
|
||||
},
|
||||
{
|
||||
"raw": {
|
||||
"a_sk": "0888cd0d3d859403f3243e0e3261ec1f0ef3145c1a5e8a382a26e1e3c8ae452b",
|
||||
"a_pk": "f74f556fd6fd8e73963875f6d825502dabe802c1a3ff9bca76b121549cb0e505",
|
||||
"pk_enc": "39bbc4529383d4e65b75658628521d5691ad30731ef503577752d45e36d63905"
|
||||
},
|
||||
"keyVersion": 44040,
|
||||
"addressVersion": 5814,
|
||||
"key": "ST154AnoeTzvZ1UqbumkGwvVaq1jds3FNEPxDgTuogYkKgPkGZwj",
|
||||
"address": "ztrMqKR8HndoCoGmPxFA7Yq2YjjyBXCzDWd5MGc9px6Uz5r8LDYvHtnQH7n7QQmZxVJTfWdCYbDqaf9yWLXn74AophUVxZZ"
|
||||
}
|
||||
],
|
||||
"invalid": {
|
||||
"fromZAddress": [
|
||||
{
|
||||
"exception": "Invalid checksum",
|
||||
"addressVersion": 5786,
|
||||
"address": "zcZviziuUB45WMY4jnV5Vmx6ZKDLyXzb12QbnWyDofTqZEbeJZF49vVNi75UgiuzXFytgJ6bJoq22gUsANBS4BjUfFudBMv"
|
||||
},
|
||||
{
|
||||
"exception": "Expected property \"1\" of type Buffer(Length: 32), got Buffer(Length: 31)",
|
||||
"addressVersion": 5786,
|
||||
"address": "E45HhnFUkK8wwgYWPqNKEmd3csM1uENCnKSjmkVbcww7UExRmJj9sWLa1hoKxfx44geMmhaSSnW1EXF1wggyRjm4cX3fiw"
|
||||
},
|
||||
{
|
||||
"exception": "PaymentAddress has unexpected data",
|
||||
"addressVersion": 5786,
|
||||
"address": "5PHzL8MmxawNatZe7Vqkvr1QBXdPVAXoB57DAXuH9WxmTKH1ueVK2vP6EpRpmCKzttZ876STgRbgwYRFzQLV3b4NV5X29CmcE"
|
||||
},
|
||||
{
|
||||
"exception": "ztLdfP3bNQ8kHeuxyZyoDHN79KKrpVJq9yMAZ4pKYup76sAG1oUH98egwYXFRjFWmUXHuuX2ypLtZE6k7ycYShJhMpw3g8K has no matching z-address",
|
||||
"addressVersion": 5786,
|
||||
"address": "ztLdfP3bNQ8kHeuxyZyoDHN79KKrpVJq9yMAZ4pKYup76sAG1oUH98egwYXFRjFWmUXHuuX2ypLtZE6k7ycYShJhMpw3g8K"
|
||||
}
|
||||
],
|
||||
"fromZKey": [
|
||||
{
|
||||
"exception": "Invalid checksum",
|
||||
"keyVersion": 43830,
|
||||
"key": "SKxthnmAMPWVea6ganJZzPjvFauoLDvgyHHbZy54qMpuFFh2L3nQ"
|
||||
},
|
||||
{
|
||||
"exception": "Expected property \"1\" of type Buffer(Length: 32), got Buffer(Length: 31)",
|
||||
"keyVersion": 43830,
|
||||
"key": "6jp6pku9TphDP795tcBn5r76ipZMU1fSpeDpi11vS1BrSpiiqCx"
|
||||
},
|
||||
{
|
||||
"exception": "SpendingKey has unexpected data",
|
||||
"keyVersion": 43830,
|
||||
"key": "2vnhhD2yvGzMBSq65i6xYcKNS3Ng3Vr3yRQtFaWUwvSvRbuSSqg77N"
|
||||
},
|
||||
{
|
||||
"exception": "ST13FvCzYJWFmDATmpVgeG14HXgHPSaNMkiU4ynyuNADq11xUEq1 has no matching z-key",
|
||||
"keyVersion": 43830,
|
||||
"key": "ST13FvCzYJWFmDATmpVgeG14HXgHPSaNMkiU4ynyuNADq11xUEq1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue