Implement shielded addresses

This commit is contained in:
Jack Grigg 2017-06-01 01:16:33 +12:00
parent 768d1bac95
commit 1f087f72d8
No known key found for this signature in database
GPG Key ID: 665DBCD284F7DAFF
7 changed files with 409 additions and 2 deletions

View File

@ -24,6 +24,8 @@
"standard": "10.0.2"
},
"dependencies": {
"bs58check": "2.0.2",
"libsodium-wrappers-sumo": "0.5.1",
"typeforce": "1.11.1"
}
}

144
src/address.js Normal file
View File

@ -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
}

View File

@ -1,3 +1,5 @@
module.exports = {
JSDescription: require('./jsdescription')
JSDescription: require('./jsdescription'),
address: require('./address')
}

View File

@ -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) {

40
src/util.js Normal file
View File

@ -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
}

114
test/address.js Normal file
View File

@ -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))
})
})
})
})

94
test/fixtures/address.json vendored Normal file
View File

@ -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"
}
]
}
}